我不是歪神 2026-02-10 17:44 采纳率: 60%
浏览 4

unityURP6+版本中后处理如何实现抓屏(语言-c#)

网上关于这个6+版本URP后处理教程太少了QWQ,我在学习这个时在测试脚本中进行抓屏,要么屏幕全黑,要么一堆报错,网上也没找到解决办法,可能是我的抓拍方式有问题,附上后处理脚本代码

using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.Universal;

public class SSPRFeature : ScriptableRendererFeature
{
    public Texture2D renderTexture1;
    public RenderTexture renderTexture2;
    public RenderTexture renderTexture3;
    public Material material;
    public SSPRPass SSPRpass;
    public string passName;
    public RenderPassEvent renderPassEvent;
    public override void Create()
    {
        SSPRpass = new SSPRPass(material, renderPassEvent, passName, renderTexture1, renderTexture2, renderTexture3);
    }
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        SetupRenderPasses(renderer, renderingData);
        renderer.EnqueuePass(SSPRpass);
    }
    public override void SetupRenderPasses(ScriptableRenderer renderer, in RenderingData renderingData)
    {
        base.SetupRenderPasses(renderer, renderingData);
        SSPRpass.SetUp();
    }
    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);
        SSPRpass.Dispose();
    }
}
public class SSPRPass : ScriptableRenderPass
{
    private Material material;
    private string PassName;
    private RenderTexture renderTexture1;
    private RTHandle RTHandle1;
    private RTHandle RTHandle2;
    private RTHandle RTHandle3;
    private class PassData
    {
        internal TextureHandle textureHandle1,textureHandle2,textureHandle3;
        internal Material material;
    }
    public SSPRPass(Material getMaterial ,RenderPassEvent getRenderPassEvent,string getPassName,Texture2D texture2D,RenderTexture renderTexture1,RenderTexture renderTexture2)
    {
        material = getMaterial;
        renderPassEvent = getRenderPassEvent;
        profilingSampler = new ProfilingSampler(getPassName);
        PassName = getPassName;

        RTHandle1 = RTHandles.Alloc(texture2D);

        RTHandle3 = RTHandles.Alloc(renderTexture2);

        this.renderTexture1 = renderTexture1;
    }
    public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
    {
        var resourceData = frameData.Get<UniversalResourceData>();
        TextureHandle cameraColorHandle = resourceData.activeColorTexture;
        using (var builder = renderGraph.AddRasterRenderPass<PassData>(PassName, out var passData))
        {
            passData.material = material;
            passData.textureHandle1 = renderGraph.ImportTexture(RTHandle1);
            passData.textureHandle2 = cameraColorHandle;
            passData.textureHandle3 = renderGraph.ImportTexture(RTHandle3);
            builder.UseTexture(passData.textureHandle1);
            builder.UseTexture(passData.textureHandle2);
            builder.SetRenderAttachment(passData.textureHandle3, 0, AccessFlags.ReadWrite);
            builder.SetRenderFunc<PassData>((PassData data, RasterGraphContext ctx) =>
            {

                data.material.SetTexture("AA", data.textureHandle1);
                data.material.SetTexture("BB", data.textureHandle2);

            });
        }
    }
    public void SetUp()
    {
        Texture2D texture2D1 = new Texture2D(renderTexture1.width, renderTexture1.height);
        Graphics.ConvertTexture(renderTexture1, texture2D1);
        RTHandle2 = RTHandles.Alloc(texture2D1);
    }

    public void Dispose()
    {
        if (RTHandle1 != null) RTHandle1.Release();
        if (RTHandle2 != null) RTHandle2.Release();
        if (RTHandle3 != null) RTHandle3.Release();

    }
}

测试shader中有正确传递AABB两张贴图

Shader "Unlit/SSPR"
{
    Properties
    {
        AA("_BlitTexture",2D) = "white"{}
        BB("_BlitTexture",2D) = "white"{}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"
            float4 _BlitScaleBias;
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D AA;
            sampler2D BB;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 aa = tex2D(AA, i.uv);
                fixed4 bb = tex2D(BB, i.uv);
                return aa * bb;
            }
            ENDCG
        }
    }
}


  • 写回答

2条回答 默认 最新

  • 檀越@新空间 2026-02-10 17:45
    关注

    下午好🌅🌅🌅
    本答案参考通义千问

    在Unity URP 6+版本中,实现后处理抓屏功能(即从渲染管线中获取屏幕图像)需要正确使用ScriptableRenderPassRenderGraph机制。你提到的“屏幕全黑”或“报错”,通常是因为资源未正确分配、纹理未正确绑定,或者渲染流程顺序错误

    下面我将详细说明如何在URP 6+中正确实现后处理抓屏,并针对你的代码进行分析和修正。


    问题分析

    你提供的代码存在以下问题:

    1. RTHandle的使用方式不正确RTHandles.Alloc()应该用于分配临时纹理,而不是直接使用Texture2D
    2. Shader参数传递错误:你在RecordRenderGraph中设置的是data.material.SetTexture("AA", data.textureHandle1);,但textureHandle1是一个TextureHandle,而SetTexture需要的是Texture类型。
    3. 没有正确设置渲染目标:在RecordRenderGraph中,你可能没有正确设置渲染目标或输出通道。
    4. 缺少对RenderTarget的引用:你需要确保renderTexture1等变量是通过RTHandle分配的。

    解决方案

    1. 修改SSPRPass类中的记录方法

    public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
    {
        var resourceData = frameData.Get<UniversalResourceData>();
        TextureHandle cameraColorHandle = resourceData.activeColorTexture;
    
        using (var builder = renderGraph.AddRasterRenderPass<PassData>(PassName, out var passData))
        {
            // 正确导入纹理
            passData.textureHandle1 = renderGraph.ImportTexture(RTHandle1);
            passData.textureHandle2 = cameraColorHandle;
            passData.textureHandle3 = renderGraph.ImportTexture(RTHandle3);
    
            // 使用这些纹理
            builder.UseTexture(passData.textureHandle1);
            builder.UseTexture(passData.textureHandle2);
            builder.UseTexture(passData.textureHandle3);
    
            // 设置渲染目标
            builder.SetRenderAttachment(passData.textureHandle3, 0, AccessFlags.ReadWrite);
    
            // 设置渲染函数
            builder.SetRenderFunc((PassData data, RasterGraphContext ctx) =>
            {
                // 正确传递纹理
                data.material.SetTexture("AA", data.textureHandle1);
                data.material.SetTexture("BB", data.textureHandle2);
            });
        }
    }
    

    注意SetTexture("AA", data.textureHandle1)data.textureHandle1 是一个 TextureHandle,不是 Texture,所以这个写法是错误的。你应该用 TextureHandle.GetTexture() 方法来获取实际的 Texture


    2. 修正 Shader 中的参数名称

    确保你的Shader中使用了正确的名称,例如:

    Properties
    {
        _AA ("AA", 2D) = "white"{}
        _BB ("BB", 2D) = "white"{}
    }
    

    然后在Shader中使用:

    sampler2D _AA;
    sampler2D _BB;
    

    3. 修正 SSPRPass 类中的 SetUp 方法

    不要使用 Graphics.ConvertTexture,而是直接通过 RTHandle 获取纹理:

    public void SetUp()
    {
        // 不要创建新的 Texture2D,而是直接通过 RTHandle 获取
        Texture2D texture2D1 = RTHandle1.Texture;
        // 或者你可以将其作为纹理使用
    }
    

    4. 确保 RenderTexture 被正确分配

    Create() 方法中,确保 RTHandle1, RTHandle2, RTHandle3 是通过 RTHandles.Alloc() 分配的:

    public override void Create()
    {
        // 假设 renderTexture1 是一个 RenderTexture
        RTHandle1 = RTHandles.Alloc(renderTexture1.width, renderTexture1.height, 
            format: RenderTextureFormat.Default, 
            colorFormat: RenderTextureFormat.Default, 
            depthFormat: DepthFormat.None);
    
        RTHandle2 = RTHandles.Alloc(renderTexture1.width, renderTexture1.height, 
            format: RenderTextureFormat.Default, 
            colorFormat: RenderTextureFormat.Default, 
            depthFormat: DepthFormat.None);
    
        RTHandle3 = RTHandles.Alloc(renderTexture1.width, renderTexture1.height, 
            format: RenderTextureFormat.Default, 
            colorFormat: RenderTextureFormat.Default, 
            depthFormat: DepthFormat.None);
    
        SSPRpass = new SSPRPass(material, renderPassEvent, passName, RTHandle1, RTHandle2, RTHandle3);
    }
    

    5. 确保在 AddRenderPasses 中正确调用 Setup

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        SetupRenderPasses(renderer, renderingData);
        renderer.EnqueuePass(SSPRpass);
    }
    

    最终修正后的完整代码

    1. SSPRFeature.cs

    using UnityEngine;
    using UnityEngine.Rendering;
    using UnityEngine.Rendering.RenderGraphModule;
    using UnityEngine.Rendering.Universal;
    
    public class SSPRFeature : ScriptableRendererFeature
    {
        public Material material;
        public string passName;
        public RenderPassEvent renderPassEvent;
    
        private SSPRPass ssprPass;
    
        public override void Create()
        {
            ssprPass = new SSPRPass(material, renderPassEvent, passName);
        }
    
        public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
        {
            SetupRenderPasses(renderer, renderingData);
            renderer.EnqueuePass(ssprPass);
        }
    
        public override void SetupRenderPasses(ScriptableRenderer renderer, in RenderingData renderingData)
        {
            base.SetupRenderPasses(renderer, renderingData);
            ssprPass.SetUp();
        }
    
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            ssprPass.Dispose();
        }
    }
    

    2. SSPRPass.cs

    using UnityEngine;
    using UnityEngine.Rendering;
    using UnityEngine.Rendering.RenderGraphModule;
    using UnityEngine.Rendering.Universal;
    
    public class SSPRPass : ScriptableRenderPass
    {
        private Material material;
        private string passName;
        private RTHandle rtHandle1, rtHandle2, rtHandle3;
    
        public SSPRPass(Material material, RenderPassEvent renderPassEvent, string passName)
        {
            this.material = material;
            this.passName = passName;
            renderPassEvent = renderPassEvent;
            profilingSampler = new ProfilingSampler(passName);
        }
    
        public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
        {
            var resourceData = frameData.Get<UniversalResourceData>();
            TextureHandle cameraColorHandle = resourceData.activeColorTexture;
    
            using (var builder = renderGraph.AddRasterRenderPass<PassData>(passName, out var passData))
            {
                passData.textureHandle1 = renderGraph.ImportTexture(rtHandle1);
                passData.textureHandle2 = cameraColorHandle;
                passData.textureHandle3 = renderGraph.ImportTexture(rtHandle3);
    
                builder.UseTexture(passData.textureHandle1);
                builder.UseTexture(passData.textureHandle2);
                builder.UseTexture(passData.textureHandle3);
    
                builder.SetRenderAttachment(passData.textureHandle3, 0, AccessFlags.ReadWrite);
    
                builder.SetRenderFunc((PassData data, RasterGraphContext ctx) =>
                {
                    // 正确传递纹理
                    data.material.SetTexture("AA", data.textureHandle1.GetTexture());
                    data.material.SetTexture("BB", data.textureHandle2.GetTexture());
                });
            }
        }
    
        public void SetUp()
        {
            rtHandle1 = RTHandles.Alloc(1024, 768, RenderTextureFormat.Default, 
                colorFormat: RenderTextureFormat.Default, 
                depthFormat: DepthFormat.None);
    
            rtHandle2 = RTHandles.Alloc(1024, 768, RenderTextureFormat.Default, 
                colorFormat: RenderTextureFormat.Default, 
                depthFormat: DepthFormat.None);
    
            rtHandle3 = RTHandles.Alloc(1024, 768, RenderTextureFormat.Default, 
                colorFormat: RenderTextureFormat.Default, 
                depthFormat: DepthFormat.None);
        }
    
        public void Dispose()
        {
            if (rtHandle1 != null) rtHandle1.Release();
            if (rtHandle2 != null) rtHandle2.Release();
            if (rtHandle3 != null) rtHandle3.Release();
        }
    
        private class PassData
        {
            internal TextureHandle textureHandle1, textureHandle2, textureHandle3;
            internal Material material;
        }
    }
    

    总结

    • 正确使用 RTHandle 是关键。
    • 避免使用 Graphics.ConvertTexture,改用 RTHandle 直接获取纹理。
    • Shader 参数名称必须与 C# 中 SetTexture 的名称一致
    • 确保渲染流程顺序正确,避免屏幕全黑。

    测试建议

    在脚本中可以添加如下代码测试是否成功抓屏:

    void OnPostRender()
    {
        Texture2D screenTex = new Texture2D(Screen.width, Screen.height);
        RenderTexture.active = renderTexture1;
        screenTex.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);
        screenTex.Apply();
        Debug.Log("Screen captured!");
    }
    

    如果你还有其他问题,比如如何将抓取的纹理保存为图片,也可以继续问我!

    评论

报告相同问题?

问题事件

  • 创建了问题 2月10日