实现自定义阴影过程中,方向光的view矩阵的构建方式

概述

在实现自定义阴影的过程中需要构建把物体从世界空间转到方向光的投影空间,和常规的M V P 变换思路一样,只不过这里的 V空间 和 P空间 变成了以 方向光为坐标原点的空间,而常规的V P空间是以摄像机为坐标原点。

下面主要说明的是 变换到方向光空间的view矩阵,常用的有四种方式。

Camera.worldToCameraMatrix 方式

实现逻辑如下:

  1. 建立一个Camera物体,放到和Directional Light物体位置一样的地方(建议座位Directional Light的子物体,然后设置Camera的rotation和localPosition都为Vector3.zero)
  2. 用该Camera物体在渲染所有Opaque物体前使用指定的shader渲染一遍场景,该shader只输出深度值, 渲染前给shader设置好VP变换矩阵(GL.GetGPUProjectionMatrix(lightCam.projectionMatrix, false) * lightCam.worldToCameraMatrix)
  3. 正常渲染场景,使用第2步中的VP变换矩阵把物体从世界空间变换到方向光的投影空间,计算深度,float depthInLS = clipPos.z * 0.5 + 0.5
  4. 计算读取shadowmap的uv, float2 uv = clipPos.xy * 0.5 + 0.5
  5. 读取shadowmap,float depthInSM = tex2D(_ShadowMap, uv)
  6. 比较depthInLSdepthInSM 的值,决定当前像素是否处于阴影中

此种方式使用一个dummy camera放到了方向光物体的位置,并设置旋转角度和方向光一致,好处是非常方便,可以直接使用Camera类的worldToCameraMatrix方法,此时变换到Camera空间就是变换到方向光的空间。而投影矩阵也可以使用现成的方法 Camera.projectionMatrix, 此时View 和Projection矩阵都具备了,要做的就是把两个矩阵乘起来即可,代码如下:

    Matrix4x4 GetMatrix_VP()
    {
        Matrix4x4 projectionMatrix = GL.GetGPUProjectionMatrix(lightCam.projectionMatrix, false);
        return projectionMatrix * lightCam.worldToCameraMatrix;
    }  

需要注意的是这里需要用 GL.GetGPUProjectionMatrix 方法来处理一下投影矩阵,以使投影矩阵能正确的变换坐标。

Camera.worldToLocalMatrix 方式

由于一下几种方式中投影矩阵的构建部分没有变化,因此只详细说明view矩阵部分。

代码如下:

Matrix4x4 view = Matrix4x4.Scale(new Vector3(1, 1, -1)) * Matrix4x4.Translate(-lightPos)  * lightTrans.worldToLocalMatrix;

乘以 Matrix4x4.Translate(-lightPos) 是因为需要进行灯光位置平移的逆运算,
乘以 Matrix4x4.Scale(new Vector3(1, 1, -1)) 是因为摄像机看向的是view空间Z轴的负方向,因为view空间使用右手坐标系而模型和世界空间使用的是左手坐标系

Matrix4x4.TRS + Quaternion.LookRotation 方式

    // view矩阵, Matrix4x4.TRS + Quaternion.LookRotation 方式 //
    Matrix4x4 GetViewMatrix_TRS_LookRotation(Vector3 lookAtPos, Vector3 camForward)
    {
        Vector3 lightPos = lookAtPos - camForward * distance;
        Quaternion rot = Quaternion.LookRotation(camForward, Vector3.up);
        return Matrix4x4.Inverse(Matrix4x4.TRS(lightPos, rot, new Vector3(1, 1, -1)));
    }


Matrix4x4.TRS + Quaternion.Euler 方式

// view矩阵, Matrix4x4.TRS + Quaternion.Euler 方式 //
    Matrix4x4 GetViewMatrix_TRS_Euler(Vector3 lookAtPos, Transform lightTrans)
    {
        Vector3 lightPos = lookAtPos - lightTrans.forward * distance;
        Quaternion rot = Quaternion.Euler(lightTrans.eulerAngles);
        return Matrix4x4.Inverse(Matrix4x4.TRS(lightPos, rot, new Vector3(1, 1, -1)));
    }


basixAxis * translate 方式

这种方式代码稍微多一些,思想是先求出方向光空间的三个基准轴向量,由这三个基准向量可以构成一个旋转矩阵,然后再由方向光的世界空间位置计算出平移矩阵,把两个矩阵相乘即可得到view矩阵。这种构建view矩阵的方式比较通用,常规的view矩阵也可以使用这种方式构建。

    // view矩阵, basixAxis * translate 方式 //
    Matrix4x4 GetViewMatrix_AxisMulTrans(Vector3 lookAtPos, Vector3 camForward)
    {
        Vector3 forward = Vector3.Normalize(-camForward);
        Vector3 up = new Vector3(0, 1, 0);
        Vector3 right = Vector3.Normalize(Vector3.Cross(up, forward));
        up = Vector3.Normalize(Vector3.Cross(forward, right));

        // translate //
        Vector3 lightPos = lookAtPos + forward * distance;
        Matrix4x4 translate = Matrix4x4.identity;
        // 也可以用 Matrix4x4.Translate(-translatePos); 构建平移矩阵 //
        translate.SetColumn(3, new Vector4(-lightPos.x, -lightPos.y, -lightPos.z, 1));
        //translate.SetColumn(3, -lightPos);

        // basicAxis //
        Matrix4x4 basicAxis = Matrix4x4.identity;
        basicAxis.SetRow(0, new Vector4(right.x, right.y, right.z, 0));
        basicAxis.SetRow(1, new Vector4(up.x, up.y, up.z, 0));
        basicAxis.SetRow(2, new Vector4(forward.x, forward.y, forward.z, 0));

        // 先平移,再计算投影在基向量的长度,即新空间的坐标 //
        return basicAxis * translate;
    }


总结

前三种方式都比较方便,但是因为使用了Unity内置的一些变量,导致具体的构建细节被隐藏,不能很好地理解view矩阵是怎么计算得出的,最后一种方式则是以view矩阵的原理为基础,使用的unity内置变量也最少,而且不需要使用dummy camera物体,完全根据参数即可构建出view矩阵,相对比较独立、可控,因此推荐使用最后一种方法。

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

推荐阅读更多精彩内容