Unity Projector 投影器原理以及优化

本文转自Unity Connect博主 dreamfairy

先上成平图

测试效果图, 图中的裤子上投影了一个眼睛

那么投影的原理是什么呢。。。 那么请看下面这张

这张图左下角就是投影器看到的景象,投影贴图“眼睛” 充满了整个投影器的视野,那么原理就呼之而出了。

在正常渲染裤子的顶点时,顺便变换到投影器的屏幕空间,然后再渲染裤子的片段处理函数中将位于投影器屏幕空间的像素都换成眼睛即可。

渲染裤子的Shader

Shader "Unlit/ProjectorShader"

{

    Properties

    {

        _MainTex ("Texture", 2D) = "white" {}

    }

    SubShader

    {

        Tags { "RenderType"="Opaque" }

        Pass

        {

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag


            #include "UnityCG.cginc"

            struct appdata

            {

                float4 vertex : POSITION;

                float2 uv : TEXCOORD0;

            };

            struct v2f

            {

                float2 uv : TEXCOORD0;

                float4 projectorUV : TEXCOORD1;

                float4 vertex : SV_POSITION;

            };

            float4x4 _ProjectorP;

            float4x4 _ProjectorV;

            float4x4 _ProjectorVP;

            sampler2D _ProjectorTex;

            sampler2D _ProjectorFallOut;

            sampler2D _MainTex;

            float4 _MainTex_ST;


            v2f vert (appdata v)

            {

                v2f o;

                o.vertex = UnityObjectToClipPos(v.vertex);

                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                float4x4 propMVP = mul(_ProjectorVP, unity_ObjectToWorld);

                float4 projProjPos = mul(propMVP, v.vertex);

                projProjPos = ComputeScreenPos(projProjPos);

                o.projectorUV = projProjPos;

                return o;

            }


            fixed4 frag (v2f i) : SV_Target

            {

                // sample the texture

                fixed4 col = tex2D(_MainTex, i.uv);


                fixed4 projectorCol = tex2Dproj(_ProjectorTex, i.projectorUV);

                // tex2Dproj = xyz/ w

                fixed4 projectorFallOutCol = tex2Dproj(_ProjectorFallOut, i.projectorUV);

                projectorCol *= projectorFallOutCol;

                col.rgb += projectorCol.rgb;

                return col;

            }

            ENDCG

        }

    }

}

自定义投影器的CS脚本

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

[ExecuteInEditMode]

public class CustomProjector : MonoBehaviour {

    public Camera ProjectorCam;

    public Texture2D Tex;

    public Texture2D FallOut;

    public Material Mat;

    private void Start()

    {


    }

    private void Update()

    {

        Matrix4x4 P = GL.GetGPUProjectionMatrix(ProjectorCam.projectionMatrix, false);

        Matrix4x4 V = ProjectorCam.worldToCameraMatrix;

        Mat.SetMatrix("_ProjectorV", V);

        Mat.SetMatrix("_ProjectorP", P);

        Mat.SetMatrix("_ProjectorVP", P * V);

        Mat.SetTexture("_ProjectorTex", Tex);

        Mat.SetTexture("_ProjectorFallOut", FallOut);

    }

}

范例中的代码借用了Unity的相机,实际上并不需要相机,仅仅是借用了相机的投影矩阵和世界空间矩阵而已。

很多项目组在制作移动端游戏时,都使用Projector来制作主角的投影,虽然比起ShadowMap是优化了许多,但是实际上只要和Projector碰撞到物件其DC 都会翻倍, 对于我来说,这还是不可接受的。

而使用上面范例的代码,可以让DC不翻倍,但是并不通用, 因为受Projector影响的物体都需要定制Shader.

Unity自带Projector会翻倍的原因主要也是通用性,跨平台,使用方便, 因此它的原理是

1.找到所有和Projector有碰撞的MeshRenderer

2.使用Projector的材质球,将MeshRenderer的顶点再渲染一遍,并贴图

也因此被投影的物体无法触发动态合批

那么问题来,有没有一种方案,既可以保证通用性,不需要定制被投影目标的Shader,又可以使DC不翻倍呢? 答案是:有的, 但是有代价

代价1:需要使用深度图

代价2:DC不会翻倍,但是总共的DC为,被投影物体数量 + 1. 既物体自带的DC + 1 * (Projector数量) 其实代价2根本不算个事

说搞就搞

1.开启相机的深度渲染

Camera.main.depthTextureMode |= DepthTextureMode.Depth;

2.创建一个表示投影器范围的网格,我搞了个Cube Mesh

3.创建Cube Mesh对应的相关矩阵,因为是Cube 因此创建的投影为正交投影, 当然,如果也可以使用透视投影。

        BoxCollider collider = this.GetComponent<BoxCollider>();

        this.m_size = collider.size.x / 2;

        this.m_nearClip = -collider.size.x / 2;

        this.m_farClip = collider.size.x / 2;

        this.m_aspect = 1;

        Matrix4x4 projector = default(Matrix4x4);

        projector = Matrix4x4.Ortho(-m_aspect * m_size, m_aspect * m_size, -m_size, m_size, m_nearClip,                m_farClip);

        m_worldToProjector = projector * this.transform.worldToLocalMatrix;

        MeshRenderer mr = this.GetComponent<MeshRenderer>();

        mr.sharedMaterial.SetMatrix("_WorldToProjector", m_worldToProjector);

好了,准备工作做完了。开始渲染吧。

首先是将投影器覆盖的区域,采样出当前屏幕空间的深度,类似这样的效果

要实现这样的效果,就是讲顶点变换到投影平面,并将坐标变换到UV值域下

大概这样

vert part

o.screenPos = ComputeScreenPos(o.vertex);

fragment part

fixed4 screenPos = i.screenPos;

screenPos.xy = screenPos.xy / screenPos.w;

float depth = tex2D(_CameraDepthTexture, screenPos).r;

好了,现在我们有了深度,下一部就是讲当前像素的深度还原回该深度对应的世界坐标了

只需要两部矩阵变换

1.从屏幕空间变换到相机空间 unity_CameraInvProjection

2.从相机空间变换到世界空间 unity_MatrixInvV

有了世界坐标后,就可以将该坐标变换到Projector的控件,就是准备工作中的 _WorldToProjector

变换到Projector空间后,还记得范例上的投影器的全部视野就是需要投射的贴图范围吗?因此这里要做UV值域的变换

//变换到自定义投影器投影空间

fixed4 projectorPos = mul(_WorldToProjector, worldSpacePos);

projectorPos /= projectorPos.w;

fixed2 projUV = projectorPos.xy * 0.5 + 0.5;  //变换到uv坐标系

fixed4 col = tex2D(_ProjectorTex, projUV);  //采样投影贴图

fixed4 mask = tex2D(_ProjectorTexMask, projUV); //采样遮罩贴图

col.rgb =  lerp(fixed3(1, 1, 1), col.rgb, (1 - mask.r)); //融合

大功告成! 你可能会好奇,为什么多了一个遮罩贴图? 虽然你讲投影器视野内的像素部分都贴了投影贴图,但是是野外的像素怎么办?这个时候就需要遮罩图抹掉,因此遮罩图的纹理设置要设置为Clamp,保证边缘像素为拉伸且外侧的Alpha为0

项目完整源码:https://github.com/dreamfairy/Unity-CubeProjector

原文链接:https://connect.unity.com/p/unity-projector-tou-ying-qi-yuan-li-yi-ji-you-hua?app=true

欢迎戳上方原文链接,下载Unity官方技术社区app,发现更多资源干货~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容