平面反射builltin&URP—— UnityShader学习记笔记


自言自语

又是好久没有更新笔记了。最近项目真的很忙。一直想更新的笔记现在才有空梳理。今天要记载的就是平面反射。

一、C# builltin

由于平面反射一般只需要传RT(RenderTexture)给shader使用。所以这次笔记就不用放上相应shader了。具体可以用平面反射实现水面效果,镜面反射,倒影等等一些列效果。

并且,由于转换矩阵都是复杂的推理和运算。超出了我这个美术的现有知识和理解范畴,因此我都是借鉴前辈的函数直接使用。自己只是根据需求做了一些变更。因此就不放相应的平面反射矩阵了。网上前辈们实现好的也很多。Unity也有封装好已经实现的函数可以直接使用。比如/利用Unity提供的 API CalculateObliqueMatrix 来做斜视锥体投影矩阵。

这里我只记录部分自己做了修改的代码。主要是提醒自己关于RT的管理以及防止RT内存泄漏的问题。

首先声明一个枚举来设置RT尺寸

//声明一个枚举类型来控制RT尺寸
    public enum RTSize
    {
        _128 = 128, _256 = 256, _512 = 512, _1024 = 1024, _2048 = 2048, _4096 = 4096, _8192 = 8192,
    }
    public RTSize ReflectTexSize = RTSize._1024;

然后进行场景初始化清理

    private void Start()
    {
        //不管场景中有没有  运行前先清掉一波反射相机
        //这里使用DestroyImmediate方法主要是要再 editmode下使其生效
        DestroyImmediate(GameObject.Find("Reflection Camera"));
        DestroyImmediate(GameObject.Find("Character Reflection Camera"));

        noTexture = new RenderTexture(1, 1, 0);
    }

在OnwillRender方法中传参并调用平面反射的具体运算

    private void OnWillRenderObject()
    {
        Shader.SetGlobalTexture("_ReflectTex", noTexture);
        Shader.SetGlobalTexture("_SourceTex", noTexture);
        Shader.SetGlobalTexture("_CHReflectTex", noTexture);
        Shader.SetGlobalTexture("_CHSourceTex", noTexture);
        PlanarReflectionBlur();
        CharacterPlanarReflectionBlur();
    }

进行场景物件的平面反射计算 (这里我把场景角色做了区分,分别用两个反射相机来做。可以实现更多自定义。原理都一样 就只放一个场景的吧)

另外Camera.main 和 Camera.current 在不同Unity版本和管线产生的观察效果不同.至于为什么不懂…可能是Unity玄学.这点在相应代码处已注释明白

 private void PlanarReflectionBlur()
    {
        //如果没有渲染 也就是脚本勾掉 则返回空  否则 设置渲染状态为true 并进行后边操作
        if (isReflectionCameraRendering == true)
            return;
        isReflectionCameraRendering = true;
        //如果没有反射相机 创建一个反射相机 
        if (!reflectionCamera)
        {
            //不管场景中有没有  运行前先清掉一波反射相机
            DestroyImmediate(GameObject.Find("Reflection Camera"));

            //先声明个物体 名字叫 Reflection Camera tag设置好
            GameObject go = new GameObject("Reflection Camera")
            {
                tag = "ReflectCamera",
            };
            //然后给这个物体添加相机组件
            reflectionCamera = go.AddComponent<Camera>();
            //并把当前相机中的相关参数 copy给反射相机
            reflectionCamera.CopyFrom(Camera.main);
            reflectionCamera.cullingMask &= ~(1 << 8);
            reflectionCamera.cullingMask &= ~(1 << 5);
            reflectionCamera.cullingMask &= ~(1 << 9);
        }
        //如果没有RT 就设置个RT 从当前缓存拿到一个RT 大小格式设置好
        if(!sourceRT)
        {
            sourceRT = RenderTexture.GetTemporary((int)ReflectTexSize / downSample, (int)ReflectTexSize / downSample, 24);
        }

        //需要实时同步相机的参数,比如编辑器下滚动滚轮,Editor相机的远近裁剪面就会变化
        UpdateCamearaParams(Camera.main, reflectionCamera);
        //设置反射相机的目标RT 为刚才声明的RT
        reflectionCamera.targetTexture = sourceRT;
        //将反射相机先设置为false  因为并不需要他真的传入屏幕图像 只是把拍到的传入RT中 所以关闭
        reflectionRT = RenderTexture.GetTemporary((int)ReflectTexSize / downSample, (int)ReflectTexSize / downSample, 24);
        Graphics.Blit(sourceRT, reflectionRT);

        reflectionCamera.enabled = false;

        //即需要保证平面模型的原点在平面上,否则可以尝试增加offset偏移 这里我就加了offsetplanar
        Transform planarPos = GetComponent<Transform>();
        planarPos.localPosition = new Vector3(0, offsetPlanar, 0);
        //根据上文平面定义,需要平面法向量和平面上任意点,此处使用transform.up为法向量,transform.position为平面上的点
        //在不懂的情况下 直接抄一个平面反射矩阵
        var reflectM = CaculateReflectMatrix(transform.up, transform.position);
        //将反射相机的转换矩阵设置为当前相机拍到的平面的反射矩阵.  注意都是在相机空间下进行的转换
        //builltin在我使用的Unity版本中有个BUG 如果 采用Camera.current相机则会造成反射相机的FOV不实时更新,需要销毁RT重新创建才生效.无法实现效果.因此只能调用Camera.main 但带来的问题就是Scene窗口和Game窗口观察效果不一致.
        //后经其他版本,包括URP管线测试,则没有此问题,可以使用Camera.current相机来使Scene窗口观察效果与Game窗口一致
        reflectionCamera.worldToCameraMatrix = Camera.main.worldToCameraMatrix * reflectM;

        //确定平面的法线朝向
        var normal = transform.up;
        //求得目标点经过平面的倒影的距离 相关原理能看懂 但矩阵着实弄不明白
        var d = -Vector3.Dot(normal, transform.position);

        //将平面到点的距离
        Vector4 plane = new Vector4(normal.x, normal.y, normal.z, d);
       
        //将平面利用相机空间的逆转置矩阵 把平面转换到反射相机空间下
        Vector4 viewSpacePlane = reflectionCamera.worldToCameraMatrix.inverse.transpose * plane;

        //利用Unity提供的 API  CalculateObliqueMatrix 来做斜视锥体投影矩阵
        var clipMatrix = Camera.main.CalculateObliqueMatrix(viewSpacePlane);
        //再把转换好的平面 传递给 反射相机的投影矩阵  使相机成像
        reflectionCamera.projectionMatrix = clipMatrix;
        reflectionCamera.depthTextureMode |= DepthTextureMode.Depth;
        //需要将背面裁剪反过来,因为仅改变了顶点,没有改变法向量,绕序反向,裁剪会不对  
        //这段也是完全没懂
        GL.invertCulling = true;
        reflectionCamera.Render();
        GL.invertCulling = false;

        //高斯模糊
        if (IsBlur == false)
        {
            如果没有反射材质 则去获取拿到当前平面的渲染组件 获得其材质
            //if (reflectionMaterial == null)
            //{
            //    Renderer renderer = GetComponent<Renderer>();
            //    //因为要可读写 所以需要用到共享材质  而不是Clone材质
            //    reflectionMaterial = renderer.sharedMaterial;
            //    //然后把相机拍到的反射RT 传到材质中
            //}
            Shader.SetGlobalTexture("_ReflectTex", sourceRT);
            Shader.SetGlobalTexture("_SourceTex", sourceRT);
        }
        else
        {
            if (blurShader == null)
            {
                //if (reflectionMaterial == null)
                //{
                //    Renderer renderer = GetComponent<Renderer>();
                //    //因为要可读写 所以需要用到共享材质  而不是Clone材质
                //    reflectionMaterial = renderer.sharedMaterial;
                //    //然后把相机拍到的反射RT 传到材质中
                //}
                Shader.SetGlobalTexture("_ReflectTex", sourceRT);
                Shader.SetGlobalTexture("_SourceTex", sourceRT);
            }
            else
            {
                if (blurMaterial == null)
                {
                    blurMaterial = new Material(blurShader);
                }
                //在这里用FOR循环引入Gaussian模糊迭代次数
                for (int i = 0; i < iteration; i++)
                {
                    //循环迭代前设置好模糊距离即shader中进行卷积操作的采样范围
                    blurMaterial.SetFloat("_BlurSize", 1.0f + i * blurSpread);
                    RenderTexture buffer = RenderTexture.GetTemporary((int)ReflectTexSize / downSample, (int)ReflectTexSize / downSample,24);

                    //然后经过shader中第一个PASS的verticalblur后,把buffer0存入到buffer中
                    Graphics.Blit(reflectionRT, buffer, blurMaterial, 0);
                    //图像再用shader中的第二个pass 进行一次horizontalblur 存入到buffer1中
                    Graphics.Blit(buffer, reflectionRT, blurMaterial, 1);
                    //此时就是已经经历过一次完整迭代的 纵横卷积的图像 存入到buffer1中了 释放buffer0的缓存 以备后用
                    RenderTexture.ReleaseTemporary(buffer);
                }
                //if (reflectionMaterial == null)
                //{
                //    Renderer renderer = GetComponent<Renderer>();
                //    //因为要可读写 所以需要用到共享材质  而不是Clone材质
                //    reflectionMaterial = renderer.sharedMaterial;
                //    //然后把相机拍到的反射RT 传到材质中
                //}
                Shader.SetGlobalTexture("_ReflectTex", reflectionRT);
                Shader.SetGlobalTexture("_SourceTex", sourceRT);
            }
        }
        RenderTexture.ReleaseTemporary(reflectionRT);
        isReflectionCameraRendering = false;
    }

最后在Disable下清除反射相机和RT


    private void OnDisable()
    {
        //清楚RT 并销毁相机
            RenderTexture.ReleaseTemporary(sourceRT);
            RenderTexture.ReleaseTemporary(reflectionRT);
            RenderTexture.ReleaseTemporary(CHsourceRT);   
            RenderTexture.ReleaseTemporary(CHreflectionRT);
            noTexture.Release();

            if (reflectionCamera)
                DestroyImmediate(reflectionCamera.gameObject);

            if (characterReflectionCamera)
                DestroyImmediate(characterReflectionCamera.gameObject);
            reflectionCamera = null;
            characterReflectionCamera = null;
    }

实时计算相机参数的方法

    //这个就是实时保持反射相机的参数和主相机参数一致
    private void UpdateCamearaParams(Camera srcCamera, Camera destCamera)
    {
        //如果目标相机和原相机有一个不存在则返回 不做操作 否则当他们都存在的时候进行如下步骤统一参数
        if (destCamera == null || srcCamera == null)
            return;
       
        destCamera.clearFlags = CameraClearFlags.SolidColor;
        destCamera.backgroundColor = Color.clear;
        destCamera.farClipPlane = srcCamera.farClipPlane;
        destCamera.nearClipPlane = srcCamera.nearClipPlane;
        destCamera.orthographic = srcCamera.orthographic;
        destCamera.fieldOfView = srcCamera.fieldOfView;
        destCamera.aspect = srcCamera.aspect;
        destCamera.orthographicSize = srcCamera.orthographicSize;
        destCamera.allowMSAA = srcCamera.allowMSAA;
        destCamera.allowHDR = true;
    }

二、URP

URP下调用相机的方法有所区别,其他都与builltin大同小异 这里只记录调用相机渲染的不同之处

        GL.invertCulling = true;
        //reflectionCamera.Render();
        //RenderSingleCamera(context,camra) 这个方法看了元数据也没懂啥意思 暂且先抄之。。后续再啃
        UniversalRenderPipeline.RenderSingleCamera(context,reflectionCamera);
        GL.invertCulling = false;

总结

C#代码学过一段时间,但是关于Unity API及各个版本和管线的变化实在有点繁杂。只能在实践中慢慢掌握了。路真的漫漫啊。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值