|
上文说到了SDF(有向向量场)的原理,实现了一个圆体的体积渲染。本文将来实现一个常见的体积渲染案例--体积光。效果如下:
https://www.zhihu.com/video/1589734709482901504
一.实现
1.创建RenderFeature
体积光是一种后处理,所以我们创建一个RenderFeature来实现比较方便。需要用到深度图,因此要把copy depth打开。
public class VolumeLightFeature : ScriptableRendererFeature
{
...
class LightRenderPass : ScriptableRenderPass
{
...
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get(Cmd_Tag);
var cameraData = renderingData.cameraData;
using (new ProfilingScope(cmd, m_ProfilingSampler))
{
Camera camera = cameraData.camera;
cmd.SetViewProjectionMatrices(Matrix4x4.identity, Matrix4x4.identity);
cmd.SetViewport(camera.pixelRect);
cmd.DrawMesh(RenderingUtils.fullscreenMesh,Matrix4x4.identity, material);
cmd.SetViewProjectionMatrices(camera.worldToCameraMatrix, camera.projectionMatrix);
}
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
...
}
LightRenderPass m_ScriptablePass;
/// <inheritdoc/>
public override void Create()
{
m_ScriptablePass = new LightRenderPass(material);
// Configures where the render pass should be injected.
m_ScriptablePass.renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
}
// Here you can inject one or multiple render passes in the renderer.
// This method is called when setting up the renderer once per-camera.
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
renderer.EnqueuePass(m_ScriptablePass);
}
}
看代码,其实是很基本的renderFeature,只是渲染一次全屏mesh。
2.shader
创建一个volumelight.shader。
首先要根据屏幕的像素点uv坐标来获取到对应的世界空间的坐标。原理是先通过屏幕uv获取到该点的深度,根据uv和深度值反算世界坐标。
#if UNITY_REVERSED_Z
real depth = tex2D(_CameraDepthTexture,i.uv);
#else
// Adjust z to match NDC for OpenGL
real depth = lerp(UNITY_NEAR_CLIP_VALUE, 1, tex2D(_CameraDepthTexture,i.uv));
#endif
float3 worldPos = ComputeWorldSpacePosition(i.uv,depth,UNITY_MATRIX_I_VP);我们有 _WorldSpaceCameraPos 内置参数 来获取了相机的世界坐标。
这样结合上文的sdf原理,可以从相机的位置,步进地扫描沿途的点,当点可以被光照射到的时候,叠加一个光量,最后所有光量的平均值就是当前屏幕像素点的体积光色。
为了方便,我们可以直接用urp内置的函数,做一些光的计算。
float4 shadowCoord = TransformWorldToShadowCoord(pos);
float shadowF = MainLightRealtimeShadow(shadowCoord);TransformWorldToShadowCoord 、MainLightRealtimeShadow 这两个方法即可算出该世界坐标pos是否有被光照射到,shadowF为1即被照射到。
因此结合上文的sdf算法,得到以下算法。
half4 Raymarch(float3 ro,float3 rd,float length)
{
half4 result = half4(0,0,0,0);
float stepLength = length / SimpleCount;
for (int i = 0; i < SimpleCount; i++) {
half3 pos = ro + i*stepLength*rd;
float4 shadowCoord = TransformWorldToShadowCoord(pos);
float shadowF = MainLightRealtimeShadow(shadowCoord);
result += step(0.1,shadowF) * _LightDensity * _LightColor;
}
result /= SimpleCount;
return result;
}由此基本的体积光算法完成。
3.优化
目前实现的光照效果太过僵硬,为了看起来更贴近现实,在渲染出光照效果后,最好再做两次blur,然后再和输出纹理融合,看起来效果更好。
二.源码
附上源码
希望大家喜欢。如果对大家有所帮助,希望能点个赞。如文章有错误,欢迎指出。 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|