Unity Ray Tracing Gem Shader 光线追踪宝石着色器

2024-03-02 08:10

本文主要是介绍Unity Ray Tracing Gem Shader 光线追踪宝石着色器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

最近在学习制作宝石材质时发现了一个 Unity 宝石的插件 R Gem Effect,第一次看这个视频的时候就觉得很惊艳,可惜这个插件在 Unity 商店里下架了。看视频可以发现,原作者使用了光线追踪,所以就想自己在 Unity 里实现这样的效果。

gems
*项目中的模型来自 R Gem Effect Unity Plugin ,HDR 环境图来自 HDRIHaven

Github: github.com/Sorumi/UnityRayTracingGem
博文原文:sorumi.xyz/posts/unity-toon-shader/

Ray Tracing

光线追踪是指从摄像机出发的若干条光线,每条光线会和场景里的物体求交,根据交点位置获取表面的材质、纹理等信息,并结合光源信息计算光照。相对于传统的光栅化渲染,光线追踪可以轻松模拟各种光学效果,如反射、折射、散射、色散等。但由于在进行求交计算时需要知道整个场景的信息,其计算成本非常高。

关于如何在 Unity 中进行 Ray Tracing,我参考了 GPU Ray Tracing in Unity这个系列的文章,使用了 Compute Shader 来实现。

实时渲染宝石

实时渲染不同于离线渲染,不可能在整个场景中都使用光线追踪,又由于宝石材质的特殊性,我对其进行一下几点约束 (tricks):

  1. 整个场景使用光栅化渲染流程,只有在渲染宝石物体时,使用光线追踪,在片元着色器中从摄像机放射光线。
  2. 每个宝石物体在光线追踪时,只和自己的 Mesh 模型进行求交点的计算。
  3. 只使用光线追踪来计算光线的折射和反射。
  4. 假设宝石表面是完全光滑的,不考虑微表面,且内部无任何杂质。其表面只有 specular 项,无 diffuse 项。
  5. 光线仅在第一次与宝石表面相交时,被分为反射光线和折射光线,之后折射光线在宝石的内部进行全反射或其折射出宝石表面。
  6. 光线经过反射或折射,射出宝石表面后,对天空球进行采样。

由于我们在着色器中传递整个模型数据,需要使用 ComputeBuffer,这一般是为 Compute Shader 提供的,要在一般的顶点像素着色器中使用,需要至少支持 shader model 4.5 。在 Shader 中,ComputeBuffer 映射的数据类型为 StructuredBuffer<T>RWStructuredBuffer<T>

折射

通过入射光线方向、入射点法线方向、入射介质折射率、出射介质折射率来计算出射光线方向:

T = η 1 η 2 ( I + cos ⁡ ( θ 1 ) N ) − N 1 − ( η 1 η 2 ) 2 ( 1 − cos ⁡ 2 ( θ 1 ) ) T=\frac{\eta_{1}}{\eta_{2}}\left(I+\cos \left(\theta_{1}\right) N\right)-N \sqrt{1-\left(\frac{\eta_{1}}{\eta_{2}}\right)^{2}\left(1-\cos ^{2}\left(\theta_{1}\right)\right)} T=η2η1(I+cos(θ1)N)N1(η2η1)2(1cos2(θ1))

当光由光密介质射到光疏介质的界面时,会发生全反射现象,即光线全部被反射回原介质内。此时:

1 − ( η 1 η 2 ) 2 ( 1 − cos ⁡ 2 ( θ 1 ) ) < 0 {1-\left(\frac{\eta_{1}}{\eta_{2}}\right)^{2}\left(1-\cos ^{2}\left(\theta_{1}\right)\right)} < 0 1(η2η1)2(1cos2(θ1))<0

由于要考虑全反射的情况,这里我自定了 Refract(i, n, eta, o) 函数,返回值表示是否存在折射光线,不存在表示进行了全反射,其实现参考了 Unity 内置的 refract(i, n, eta) 函数。

float Refract(float3 i, float3 n, float eta, inout float3 o)
{float cosi = dot(-i, n);float cost2 = 1.0f - eta * eta * (1 - cosi * cosi);o = eta * i + ((eta * cosi - sqrt(cost2)) * n);return 1 - step(cost2, 0);
}

在和模型的三角面求交点时,折射光线会和三角面的背面相交,需要注意不能进行背面剔除。当光线从宝石射出时,法线需要反向

float eta;
float3 normal;// out
if (dot(ray.direction, hit.normal) > 0)
{normal = -hit.normal;eta = _IOR;
}
// in
else
{normal = hit.normal;eta = 1.0 / _IOR;
}ray.origin = hit.position - normal * 0.001f;float3 refractRay;
float refracted = Refract(ray.direction, normal, eta, refractRay);……// Refraction
if (refracted == 1.0)ray.direction = refractRay;
// Total Internal Reflection
elseray.direction = reflect(ray.direction, normal);

仅有折射的效果:
gem_refraction

反射

通过入射光线方向、入射点法线方向来计算反射光线方向:

R = I − 2 ( N ⋅ I ) N R=I-2(N \cdot I) N R=I2(NI)N

可以直接使用 Unity 的内置函数 reflect(i, n)

float3 reflect(float3 i, float3 n)
{return i - 2.0 * n * dot(n, i);
}

仅有反射的效果:
gem_reflection

菲涅尔

透明物体既有反射又有透射即折射。它们反射的光量与透射的光量取决于入射角。当入射角减小时,透射的光量增加。按照能量守恒的原理,反射光的量加上透射光的量必须等于入射光的总量,因此,入射角增加时,反射的光量增加。

反射光与折射光的数量可以使用菲涅尔方程来计算。这里我使用 Schlick 的简化版本 来计算 Fresnel 的值,采用了入射光线方向、入射点法线方向、入射介质折射率、出射介质折射率:

R ( θ ) = R 0 + ( 1 − R 0 ) ( 1 − cos ⁡ θ ) 5 R 0 = ( n 1 − n 2 n 1 + n 2 ) 2 \begin{aligned} R(\theta) &=R_{0}+\left(1-R_{0}\right)(1-\cos \theta)^{5} \\ R_{0} &=\left(\frac{n_{1}-n_{2}}{n_{1}+n_{2}}\right)^{2} \end{aligned} R(θ)R0=R0+(1R0)(1cosθ)5=(n1+n2n1n2)2

float FresnelSchlick(float3 normal, float3 incident, float ref_idx)
{float cosine = dot(-incident, normal);float r0 = (1 - ref_idx) / (1 + ref_idx); // ref_idx = n2/n1r0 = r0 * r0;return r0 + (1 - r0) * pow((1 - cosine), 5);
}

在第一次与物体表面相交时,计算 Fresnel 的值,反射量乘以 F R F_{R} FR , 投射量乘以 1 − F R 1-F_{R} 1FR

if (depth == 0)
{float3 reflectDir = reflect(ray.direction, hit.normal);reflectDir = normalize(reflectDir);float3 reflectProb = FresnelSchlick(normal, ray.direction, eta) * _Specular;specular = SampleCubemap(reflectDir) * reflectProb;ray.energy *= 1 - reflectProb;
}

使用菲涅尔融合折射和反射的效果:
gem_fresnel

光线吸收

折射光在透明物体内部进行传播,根据 Beer-Lambert 定律,光照射入一吸收介质表面,在通过一定厚度后,介质吸收了一部分光能,透射光的强度响应减弱,因此介质会呈现出颜色倾向。光穿过一个体积的透射比 T T T 为:

T = e − σ a d T=e^{-\sigma_{a} d} T=eσad

这里 σ a \sigma_{a} σa 为一个吸收系数, d d d 为光折射传播的距离。

这是具有 Beer-Lambert 定律的立方体,可吸收远距离的红色和绿色光。可以发现光线透过距离越长的部分,颜色越深。

absorption

要应用 Beer-Lambert 定律,您首先要计算射线穿过吸收介质的距离,在 Ray 中增加变量 absorbDistance

struct Ray
{float3 origin;float3 direction;float3 energy;float absorbDistance;
};

Shade 函数中对累加计算:

float3 Shade(inout Ray ray, RayHit hit, int depth)
{if (hit.distance < 1.#INF && depth < (_TraceCount - 1)){……if (depth != 0)ray.absorbDistance += hit.distance;……}
}

最后在计算颜色时根据吸收率和穿过距离计算透射比,吸收率一般为一个颜色值,描述了每个颜色通道在远处吸收的数量,为了使材质调整起来更加直观,这里对 _Color 值进行取反,与 _AbsorbIntensity 相乘,表示吸收率。

float3 Shade(inout Ray ray, RayHit hit, int depth)
{if (hit.distance < 1.#INF && depth < (_TraceCount - 1)){……}else{ray.energy = 0.0f;float3 cubeColor = SampleCubemap(ray.direction);float3 absorbColor = float3(1.0, 1.0, 1.0) - _Color;float3 absorb = exp(-absorbColor * ray.absorbDistance * _AbsorbIntensity);return cubeColor * absorb * _ColorMultiply + _Color * _ColorAdd;}
}

增加光线吸收的效果:
gem_absorption

扩展阅读

“Cheap” Diamond Rendering

这篇文章中通过烘焙模型内部的法线贴图为 Cubemap,来模拟 RayTracing,速度更快。但这种方法仍然有点费,每个像素点最多要进行 7 次 Cubemap 采样。而且在法线转折处会有明显的锯齿,虽然能通过提高 Cubemap 的精度来改善,但不能完全消除。

参考

GPU Ray Tracing in Unity

Ray Tracing in One Weekend

Triangle Intersection

Reflection, Refraction and Fresnel

Raytracing Reflection, Refraction, Fresnel, Total Internal Reflection, and Beer’s Law

Microfacet models for refraction through rough surfaces.

Extending the Disney BRDF to a BSDF with Integrated Subsurface Scattering

这篇关于Unity Ray Tracing Gem Shader 光线追踪宝石着色器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/765467

相关文章

图解可观测Metrics, tracing, and logging

最近在看Gophercon大会PPT的时候无意中看到了关于Metrics,Tracing和Logging相关的一篇文章,凑巧这些我基本都接触过,也是去年后半年到现在一直在做和研究的东西。从去年的关于Metrics的goappmonitor,到今年在排查问题时脑洞的基于log全链路(Tracing)追踪系统的设计,正好是对这三个话题的实践。这不禁让我对它们的关系进行思考:Metrics和Loggi

Unity Post Process Unity后处理学习日志

Unity Post Process Unity后处理学习日志 在现代游戏开发中,后处理(Post Processing)技术已经成为提升游戏画面质量的关键工具。Unity的后处理栈(Post Processing Stack)是一个强大的插件,它允许开发者为游戏场景添加各种视觉效果,如景深、色彩校正、辉光、模糊等。这些效果不仅能够增强游戏的视觉吸引力,还能帮助传达特定的情感和氛围。 文档

Unity协程搭配队列开发Tips弹窗模块

概述 在Unity游戏开发过程中,提示系统是提升用户体验的重要组成部分。一个设计良好的提示窗口不仅能及时传达信息给玩家,还应当做到不干扰游戏流程。本文将探讨如何使用Unity的协程(Coroutine)配合队列(Queue)数据结构来构建一个高效且可扩展的Tips弹窗模块。 技术模块介绍 1. Unity协程(Coroutines) 协程是Unity中的一种特殊函数类型,允许异步操作的实现

Unity 资源 之 Super Confetti FX:点亮项目的璀璨粒子之光

Unity 资源 之 Super Confetti FX:点亮项目的璀璨粒子之光 一,前言二,资源包内容三,免费获取资源包 一,前言 在创意的世界里,每一个细节都能决定一个项目的独特魅力。今天,要向大家介绍一款令人惊艳的粒子效果包 ——Super Confetti FX。 二,资源包内容 💥充满活力与动态,是 Super Confetti FX 最显著的标签。它宛如一位

Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(4)

本文仅作笔记学习和分享,不用做任何商业用途 本文包括但不限于unity官方手册,unity唐老狮等教程知识,如有不足还请斧正​​ Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(3)-CSDN博客  这节就是真正的存储数据了   理清一下思路: 1.存储路径并检查 //2进制文件类存储private static string Data_Binary_Pa

Unity Adressables 使用说明(一)概述

使用 Adressables 组织管理 Asset Addressables 包基于 Unity 的 AssetBundles 系统,并提供了一个用户界面来管理您的 AssetBundles。当您使一个资源可寻址(Addressable)时,您可以使用该资源的地址从任何地方加载它。无论资源是在本地应用程序中可用还是存储在远程内容分发网络上,Addressable 系统都会定位并返回该资源。 您

Unity Adressables 使用说明(六)加载(Load) Addressable Assets

【概述】Load Addressable Assets Addressables类提供了加载 Addressable assets 的方法。你可以一次加载一个资源或批量加载资源。为了识别要加载的资源,你需要向加载方法传递一个键或键列表。键可以是以下对象之一: Address:包含你分配给资源的地址的字符串。Label:包含分配给一个或多个资源的标签的字符串。AssetReference Obj

在Unity环境中使用UTF-8编码

为什么要讨论这个问题         为了避免乱码和更好的跨平台         我刚开始开发时是使用VS开发,Unity自身默认使用UTF-8 without BOM格式,但是在Unity中创建一个脚本,使用VS打开,VS自身默认使用GB2312(它应该是对应了你电脑的window版本默认选取了国标编码,或者是因为一些其他的原因)读取脚本,默认是看不到在VS中的编码格式,下面我介绍一种简单快

Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(3)

本文仅作笔记学习和分享,不用做任何商业用途 本文包括但不限于unity官方手册,unity唐老狮等教程知识,如有不足还请斧正​​ Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(2) (*****生成数据结构类的方式特别有趣****)-CSDN博客 做完了数据结构类,该做一个存储类了,也就是生成一个字典类(只是声明)  实现和上一节的数据结构类的方式大同小异,所

【Unity小技巧】URP管线遮挡高亮效果

前言 在URP渲染管线环境下实现物体遮挡高亮显示效果,效果如下: Unity URP遮挡高亮 实现步骤 创建层级,为需要显示高亮效果的物体添加层级,比如Player 创建一个材质球,也就是高亮效果显示的材质球找到Universal Renderer Data Assets 4.在Assets上添加两个Render Objects组件 第一个做如下三处设置 指定遮挡层级指