(First time asking a question here so I do apologise if I’ve somehow submitted it wrong/etc)
I’m trying to get to grips with ScriptableRenderFeatures (6000.0.30f1) and I’m struggling with handling textures in my custom passes.
I’m finding the TextureHandle / RTHandle / RenderTextureDescriptor / TextureDesc APIs pretty confusing, but I’ve successfully got my created texture both into the next render pass and out as a global texture for use in other shaders. But I’m still having difficulty with the mip levels.
I’ve been referring to the documentation here:
Unity - Manual: Example of a complete Scriptable Renderer Feature in URP
Unity - Manual: Transfer a texture between render passes in URP
My use case is I’m trying to implement a Hi-Z map for later use in occlusion culling of vegetation. I’ve got two custom ScriptableRenderPass-es : one raster pass to render the scene depth of the occluding geometry, followed by a compute shader pass to fill the depth map’s mips with the downsampled values.
(My depth map is a single channel “color” texture because the depth-only format doesn’t support mip levels.)
The code works and I can view the generated mipmaps by accessing the global texture. But it generates a pile of errors on some (I think not all?) frames:
Attempting to bind MIP 4 of Texture ID 1007 as an UAV, but the texture only has 4 MIP levels!
+etc for MIPs 5 through 7. The textures are generated with mipLevels = 8 (and indeed I can view all 8) so I don’t understand why it only has 4 at the point of binding.
(My best guesses are A) something something asynchronous? or B) something like mip streaming?)
What’s going on here? How do I force the compute pass to successfully access all 8 mip levels? Why does it work even though it throws these errors?
My render passes:
class TerrainDepthPass : ScriptableRenderPass
{
private Material material;
private RenderTextureDescriptor hiZRenderTextureDescriptor;
private TextureDesc hiZTextureDesc;
private const string k_HiZTextureName = "_HiZTexture";
private int hiZTextureID = Shader.PropertyToID(k_HiZTextureName);
private int2 resolution;
private class PassData
{
public RendererListHandle objectsToDraw;
}
public TerrainDepthPass(Material material, int mipLevels)
{
this.material = material;
resolution = new int2(Screen.width, Screen.height);
resolution /= 4;
hiZRenderTextureDescriptor = new RenderTextureDescriptor(
resolution.x, resolution.y,
RenderTextureFormat.RFloat, 0,
mipLevels, RenderTextureReadWrite.Linear);
hiZTextureDesc = new TextureDesc(hiZRenderTextureDescriptor);
hiZTextureDesc.useMipMap = true;
hiZTextureDesc.autoGenerateMips = false;
hiZTextureDesc.enableRandomWrite = true;
}
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
resolution = new int2(cameraData.cameraTargetDescriptor.width,
cameraData.cameraTargetDescriptor.height);
resolution /= 4;
hiZTextureDesc.width = resolution.x;
hiZTextureDesc.height = resolution.y;
TextureHandle dst = renderGraph.CreateTexture(hiZTextureDesc);
if (!dst.IsValid())
return;
using (var builder = renderGraph.AddRasterRenderPass<PassData>(
"HiZTerrainDepth", out var passData))
{
UniversalRenderingData renderingData = frameData.Get<UniversalRenderingData>();
UniversalLightData lightData = frameData.Get<UniversalLightData>();
SortingCriteria sortFlags = cameraData.defaultOpaqueSortFlags;
RenderQueueRange renderQueueRange = RenderQueueRange.opaque;
int layerMask = ~0;
//int layerMask = (1 << 8);
FilteringSettings filterSettings = new FilteringSettings(renderQueueRange, layerMask: layerMask);
ShaderTagId shadersToOverride = new ShaderTagId("UniversalForward");
DrawingSettings drawSettings = RenderingUtils.CreateDrawingSettings(
shadersToOverride, renderingData, cameraData, lightData, sortFlags);
drawSettings.overrideMaterial = material;
var rendererListParams = new RendererListParams(
renderingData.cullResults, drawSettings, filterSettings);
passData.objectsToDraw = renderGraph.CreateRendererList(rendererListParams);
builder.UseRendererList((passData.objectsToDraw));
builder.SetRenderAttachment(dst, 0, AccessFlags.Write);
builder.SetRenderFunc((PassData data, RasterGraphContext context) =>
ExecutePass(data, context));
builder.SetGlobalTextureAfterPass(dst, hiZTextureID);
DepthContext depthContext = frameData.Create<DepthContext>();
depthContext.hiZDepthTexture = dst;
}
}
static void ExecutePass(PassData passData, RasterGraphContext context)
{
// Clear the render target to black
context.cmd.ClearRenderTarget(true, true, Color.black);
// Draw the objects in the list
context.cmd.DrawRendererList(passData.objectsToDraw);
}
}
public class DownsamplePass : ScriptableRenderPass
{
private ComputeShader shader;
private int mipLevels;
static int idSizePrev = Shader.PropertyToID("_SizePrev");
static int idSizeNew = Shader.PropertyToID("_SizeNew");
static int idDepthMapPrev = Shader.PropertyToID("_DepthMapPrev");
static int idDepthMapNew = Shader.PropertyToID("_DepthMapNew");
public DownsamplePass(ComputeShader shader, int mipLevels)
{
//Debug.Log("DownsamplePass ctor");
this.shader = shader;
this.mipLevels = mipLevels;
}
private class PassData
{
public ComputeShader computeShader;
public int mipLevels;
public TextureHandle depthTexture;
public int[] origSize;
}
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
using (var builder = renderGraph.AddComputePass("HiZDownsamplePass", out PassData passData))
{
builder.AllowPassCulling(false);
passData.computeShader = shader;
passData.mipLevels = mipLevels;
DepthContext depthContext = frameData.Get<DepthContext>();
passData.depthTexture = depthContext.hiZDepthTexture;
builder.UseTexture(passData.depthTexture);
passData.origSize = new int[] { depthContext.hiZDepthTexture.GetDescriptor(renderGraph).width,
depthContext.hiZDepthTexture.GetDescriptor(renderGraph).height };
builder.SetRenderFunc((PassData passData, ComputeGraphContext context) =>
ExecutePass(passData, context));
}
}
static void ExecutePass(PassData passData, ComputeGraphContext context)
{
TextureHandle depthTexture = passData.depthTexture;
ComputeShader shader = passData.computeShader;
int[] sizePrev = passData.origSize;
int[] sizeNew;
for (int newMip = 1; newMip < passData.mipLevels; newMip++)
{
sizeNew = new int[] { Mathf.CeilToInt((float)sizePrev[0] / 2f),
Mathf.CeilToInt((float)sizePrev[1] / 2f)};
sizeNew[0] = math.max(sizeNew[0], 1);
sizeNew[1] = math.max(sizeNew[1], 1);
context.cmd.SetComputeIntParams(shader, idSizePrev, sizePrev);
context.cmd.SetComputeIntParams(shader, idSizeNew, sizeNew);
context.cmd.SetComputeTextureParam(shader, 0, idDepthMapPrev, depthTexture, mipLevel: newMip - 1);
context.cmd.SetComputeTextureParam(shader, 0, idDepthMapNew, depthTexture, mipLevel: newMip);
int groupSize_X = Mathf.CeilToInt((float)sizeNew[0] / 8f);
int groupSize_Y = Mathf.CeilToInt((float)sizeNew[1] / 8f);
context.cmd.DispatchCompute(shader, 0, groupSize_X, groupSize_Y, 1);
sizePrev = sizeNew;
}
}
}
I could share the shaders too, but they seem to be doing their job so I don’t think the problem is with the shaders…