草庐IT

Unity渲染菜B之 普通版平面反射

qq_37373780 2023-07-01 原文

1.先看效果

2.简介

其实原理是设置一个和MainCamera关于反射平面的XoZ面镜像的摄像机,拍摄反射内容作为纹理传递给反射平面的shader进行绘制。Unity的老示例项目Angry Bot和公司的实现都是这种方法,算是一种比较古早的方案了。适合前向渲染(SSR要走延迟管线)。

这里做起来只考虑反射面是一个完全的平面,没有任何凹凸的地方。

另外本人只是渲染菜鸡,以下内容若有概念错误或者说不明白的地方还请轻喷= =

3.实现原理

注意:贴出来的代码用的变量名和图片讲解里的不一致,但原理是基本一样的。

3.1 设置反射摄像机的位置和朝向

反射摄像机的位置和朝向都要和主摄像机关于平面(的XoZ面)镜像。

3.1.1 先求反射摄像机位置

  1. 先求出主摄像机到反射平面transform中心的向量 V1,通过两者的position相减得到。

  2. 求V1在平面的y轴方向上的投影Vtmp ,它垂直于平面向下,Vtmp的模(就是长度)是主摄像机到反射平面的距离。

  3. 主摄像机的position + 2*Vtmp = 反射摄像机的position。

		Vector3 v1 = plane.position - MainCam.transform.position;
        v1 = Vector3.Project(v1, plane.up);
        ......
        ReflCam.transform.position = MainCam.transform.position + 2f * v1;

3.1.2 再求反射摄像机的朝向

  1. 以主摄像机的forward方向为V2,这是个单位向量。
  2. V2在反射平面的y轴上的投影向量VpVp方向和反射平面的y轴正方向相反。
  3. V2’ = V2 - 2 * VpV2’ 就是关于反射平面和V2镜像的单位向量。
  4. 令反射摄像机的forward = V2’,完事。
		Vector3 mainCamForward = MainCam.transform.forward;
        Vector3 v2 = - Vector3.Project(mainCamForward, plane.up);
		......
        ReflCam.transform.forward = mainCamForward + 2 * v2;//求反射相机的朝向

3.1.3 检查效果

随意旋转下反射平面,康康反射摄像机的位置、朝向能否都和主摄像机关于平面镜像,康康拍摄到的反射画面正不正常。

3.2 反射摄像机渲染到RT

3.2.1 调整反射摄像机设置

调整下反射摄像机的各项参数(和主摄像机的参数保持一致),防止后面出现各种各样奇怪的问题。下面是我自己的设置,各位可以根据自己的实际情况做加减。

//我把反射摄像机disable了,不影响,可用ReflCam.Render()强制调用一次渲染
ReflCam.enabled = false;
//透视or正交和主摄像机一致
if (MainCam.orthographic != ReflCam.orthographic) 
		ReflCam.orthographic = MainCam.orthographic;
//远近裁剪平面、fov一致
if (MainCam.nearClipPlane != ReflCam.nearClipPlane) 
        ReflCam.nearClipPlane = MainCam.nearClipPlane;
if (MainCam.farClipPlane != ReflCam.farClipPlane) 
		ReflCam.farClipPlane = MainCam.farClipPlane;
if (MainCam.fieldOfView != ReflCam.fieldOfView) 
		ReflCam.fieldOfView = MainCam.fieldOfView;
//反射摄像机的clearFlag也是skybox(天空的内容也要反射的嘛)
if (ReflCam.clearFlags != CameraClearFlags.Skybox) 
		ReflCam.clearFlags = CameraClearFlags.Skybox;
//可以单独给要反射的物体加个layer,让反射摄像机只渲染这个layer的物体
ReflCam.cullingMask = m_ReflectedObjsMask;

3.2.2 调整RT设置,反射摄像机渲染到RT

/*
这里的width和height应该分别对应窗口分辨率的宽和高。如果不希望搞太大的反射图像,可以让width和height都乘上一个相同的倍率
*/
RenderTexture rt = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32);
rt.name = plane.name + " ReflTex";//命名随意
/*
要不要开mipmap视实际项目需求来定。有时候反射平面可能是粗糙的,不太可能反射出精细的图像。这时候结合自定的mipmap level就能采样出较粗糙的反射图像。
*/
rt.useMipMap = true;
/*
wrapMode可随意,如果反射物超出反射摄像机的视觉范围,可能会出现对应的异常图像。
*/
rt.wrapMode = TextureWrapMode.Clamp;
//反射摄像机内容渲染到rt
targetRT = rt;
......
ReflCam.targetTexture = targetRT;
ReflCam.Render();//调用渲染

3.3 把反射纹理绘制到平面上

3.3.1 怎么绘制?

先看这张效果图,想象一下如果主摄像机保持不变,我们转到其他位置看,会是什么效果?

比如转换到左视图看,可以看到反射物的图像被绘制在它自己脚下往主摄像机方向的那个范围里,在主摄像机能看到正确的结果。观察反射摄像机采样的光路图,其实在像素着色器中把平面上的点转换到反射摄像机的屏幕空间里,得到对应的uv,用这个uv对反射纹理进行采样,不就可以了?

3.3.2 传递反射摄像机的V、P变换矩阵和反射纹理给shader

反射平面上各顶点的M变换(模型空间转世界空间)会在shader内完成,我们只需把完成摄像机空间内视图和投影变换的矩阵P*V传到shader里即可。

	internal struct Uniforms
    {
        public static int ReflTex = Shader.PropertyToID("_ReflTex");
        public static int ReflSpaceMatrixVP = Shader.PropertyToID("_ReflSpaceMatrixVP");
		......
    }
    
    //用访问器提供P*V矩阵
    private Matrix4x4 ReflSpaceMatrixVP
    {
        get
        {
            if (ReflCam == null)
            {
                Debug.LogError("ReflCam is null!");
                return Matrix4x4.zero;
            }
            Matrix4x4 V = ReflCam.worldToCameraMatrix;
            Matrix4x4 P = GL.GetGPUProjectionMatrix(ReflCam.projectionMatrix, false);
            return P * V;
        }
    }
        //这里我用了MaterialPropertyBlock,如果只是简单测试,用Renderer的material也行了
        ......
        renderer.GetPropertyBlock(mpb);
        mpb.SetTexture(Uniforms.ReflTex, rt);//传递反射纹理
        mpb.SetMatrix(Uniforms.ReflSpaceMatrixVP, ReflSpaceMatrixVP);//传递P*V矩阵
        renderer.SetPropertyBlock(mpb);
        ......

3.3.3 编写shader的反射采样部分

把整个shader贴上来好了。其中的重点部分是WorldToReflectionPos函数。

Shader "Unlit/ReflectionPlane"
{
    Properties
    {
        _MipmapLevel("Mipmap Level", Range(0,7)) = 0
        _ReflTex("Refl Texture",2D) = "black"{}
    }

    CGINCLUDE

        #include "UnityCG.cginc"
        int _MipmapLevel;

        sampler2D _ReflTex;
        float4x4 _ReflSpaceMatrixVP;

        struct appdata
            {
                float4 vertex : POSITION;
                fixed2 uv : TEXCOORD0;
            };

        struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float4 worldPos : TEXCOORD1;
            };

        fixed4 WorldToReflectionPos(float4 worldPos, inout fixed2 uv_ReflSpace)
        {
            float4 refl_space_pos = mul(_ReflSpaceMatrixVP, worldPos);//转换到反射摄像机的投影空间
            uv_ReflSpace = refl_space_pos.xy/refl_space_pos.w;//先进行透视除法把xy映射到[-1,1]区间
            uv_ReflSpace = uv_ReflSpace * 0.5 + 0.5;//再把xy映射到[0,1]区间,可得反射用的uv
            return refl_space_pos;
        }

        v2f vertRefl (appdata v)
        {
            v2f o;
            o.vertex = UnityObjectToClipPos(v.vertex);
            o.worldPos = mul(unity_ObjectToWorld, v.vertex);
            o.uv = v.uv;
            return o;
        }

        fixed4 fragRefl (v2f i) : SV_Target
        {
            fixed2 uv_refl;
            fixed4 reflPos = WorldToReflectionPos(i.worldPos, uv_refl);
            //如果只是简单采样可用tex2D
            fixed3 reflect_col = tex2Dlod(_ReflTex,fixed4(uv_refl, 0 ,_MipmapLevel));

            return fixed4(reflect_col,1);
        }


    ENDCG

    SubShader
    {
        Pass
        {
            Tags { "RenderType"="Opaque" "RenderQueue"="Geometry" "LightMode"="ForwardBase"}
            LOD 100
            CGPROGRAM
            #pragma vertex vertRefl
            #pragma fragment fragRefl
            ENDCG
        }
    }
}

至此一个基础的平面反射就完成了,转动一下平面看结果是否正确。一般项目用上这个就差不多可以了,后续会考虑发下自己做过的一些小改动。(其实都是些没什么卵用的改动)

有关Unity渲染菜B之 普通版平面反射的更多相关文章

  1. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  2. ruby-on-rails - Rails HTML 请求渲染 JSON - 2

    在我的Controller中,我通过以下方式在我的index方法中支持HTML和JSON:respond_todo|format|format.htmlformat.json{renderjson:@user}end在浏览器中拉起它时,它会自然地以HTML呈现。但是,当我对/user资源进行内容类型为application/json的curl调用时(因为它是索引方法),我仍然将HTML作为响应。如何获取JSON作为响应?我还需要说明什么? 最佳答案 您应该将.json附加到请求的url,提供的格式在routes.rb的路径中定义。这

  3. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  4. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  5. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  6. unity---接入Admob - 2

    目录1.AdmobSDK下载地址2.将下载好的unityPackagesdk导入到unity里​编辑 3.解析依赖到项目中

  7. Unity 3D 制作开关门动画,旋转门制作,推拉门制作,门把手动画制作 - 2

    Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u

  8. ruby-on-rails - Rails 渲染带有驼峰命名法的 json 对象 - 2

    我在一个简单的RailsAPI中有以下Controller代码:classApi::V1::AccountsControllerehead:not_foundendendend问题在于,生成的json具有以下格式:{id:2,name:'Simpleaccount',cash_flows:[{id:1,amount:34.3,description:'simpledescription'},{id:2,amount:1.12,description:'otherdescription'}]}我需要我生成的json是camelCase('cashFlows'而不是'cash_flows'

  9. ruby-on-rails - 使用 header 渲染 JSON - 2

    我想在我的Controller中使用以下corsheader呈现JSON:'Access-Control-Allow-Origin'='*'.我试过这个:defmy_actionrender(json:some_params)response.headers['Access-Control-Allow-Origin']='*'end但是我得到了一个AbstractController::DoubleRenderError。有没有办法使用header呈现JSON? 最佳答案 您不能在渲染后设置header,因为已发送响应。所以在没有意

  10. 建模分析 | 平面2R机器人(二连杆)运动学与动力学建模(附Matlab仿真) - 2

    目录0专栏介绍1平面2R机器人概述2运动学建模2.1正运动学模型2.2逆运动学模型2.3机器人运动学仿真3动力学建模3.1计算动能3.2势能计算与动力学方程3.3动力学仿真0专栏介绍?附C++/Python/Matlab全套代码?课程设计、毕业设计、创新竞赛必备!详细介绍全局规划(图搜索、采样法、智能算法等);局部规划(DWA、APF等);曲线优化(贝塞尔曲线、B样条曲线等)。?详情:图解自动驾驶中的运动规划(MotionPlanning),附几十种规划算法1平面2R机器人概述如图1所示为本文的研究本体——平面2R机器人。对参数进行如下定义:机器人广义坐标

随机推荐