赞
踩
又是好久没有更新笔记了。最近项目真的很忙。一直想更新的笔记现在才有空梳理。今天要记载的就是平面反射。
由于平面反射一般只需要传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下调用相机的方法有所区别,其他都与builltin大同小异 这里只记录调用相机渲染的不同之处
GL.invertCulling = true;
//reflectionCamera.Render();
//RenderSingleCamera(context,camra) 这个方法看了元数据也没懂啥意思 暂且先抄之。。后续再啃
UniversalRenderPipeline.RenderSingleCamera(context,reflectionCamera);
GL.invertCulling = false;
C#代码学过一段时间,但是关于Unity API及各个版本和管线的变化实在有点繁杂。只能在实践中慢慢掌握了。路真的漫漫啊。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。