[unity/shaderlab]阴影学习笔记

开篇胡扯

      之前在学习入门精要的时候,看到阴影部分各种名字超长的内置宏和看不明白的采样方式就想直接跳过这一章了(当时想的是,反正用到的时候把这些内置宏复制一遍出来就可以了)。直到前段时间面试被问到unity内部阴影到底是怎么实现的,才发现自己对阴影一无所知,刚好最近有时间,准备再次认真的学习一下阴影相关的知识。


1.阴影必须的三要素

      想要产生阴影,至少需要三个物体的支持:

  • 光源(废话)
  • 阴影产生(投射?)者
  • 阴影接收者
大概就是这样的~

      阴影的产生与消失与这三个物体息息相关,所以当场景中阴影消失时要先查找一下是不是这三个物体没有打开对应的开关。

组件中阴影的开关

      light组件中的mode和shadow type选项都会影响阴影的产生,mesh组件重的cast shadows表示是否向其他物体投射阴影,receive shadows表示这个mesh是否接收其他物体的阴影。

2.阴影是怎么产生的

      我们知道,阴影是由于物体遮住了光的传播,不能穿过不透明物体而形成的较暗区域。
      而在unity中,阴影使用的是一种Shadow Map的技术。大概意思就是把相机放在光源位置,相机看不到的地方,就是这个光源的阴影区域。

3.阴影产生着(投射者)

      上面提到过unity使用的是Shadow Map技术,unity要把相机放在光源位置,然后计算一张该点的深度图。如果按照正常流程来说,我们要把物体的渲染流程全都走一遍来写入深度,得到shadowmap,但是这么做无疑会做很多多余的计算(很多和深度无关的计算如光照模型等)。
      unity使用了一个额外的pass来专门更新光源的shadowmap:这个pass就是LightMode标签被设置为ShadowCaster的pass。所以,在unity中,如果没有这个pass,并且没有指定Fallback或指定的Fallback中没有LightMode标签为ShadowCaster的pass时,该物体就无法向其他物体投射阴影。

Tags { "LightMode" = "ShadowCaster" }

FarmeDebug中渲染shadowmap的事件

      对于不透明物体,我们一般可以使用unity中写好的shader作为Fallback,这样在渲染的时候unity会自己去Fallback中寻找ShadowCaster的pass来渲染阴影。但是对于透明物体或者是使用了透明度测试的材质,使用默认的ShadowCaster就会得到一些错误的结果,这个时候就要我们根据自己的需要来实现ShadowCaster的pass,比如在片元着色器进行透明度测试等。

4.阴影接收者

      在传统的阴影映射纹理中,我们会在正常渲染的pass中把顶点转换到光源空间下,然后对光源的shadowmap进行采样,再把采样结果与顶点的深度进行对比,如果顶点深度大于采样结果,那么说明该顶点在阴影内。
      unity使用了不同的阴影采样技术:屏幕空间的阴影映射技术。但是不是所有平台unity都会使用这种技术,因为这种技术需要显卡支持MRT。那么屏幕空间的阴影映射技术到底做了什么呢?
      unity首先会调用LightMode为ShadowCaster的pass得到光源的阴影映射纹理(shadowmap)摄像机的深度纹理。然后根据阴影映射纹理和相机的深度纹理得到屏幕空间的阴影图。如果摄像机的深度图中记录的深度大于转换到阴影映射纹理中的深度值,就说明该表面可见但是处于阴影中。
      如果我们想要某个物体接受来自其他物体的阴影,只需要在shader中对这张屏幕空间的阴影图进行采样就可以了。因为阴影图是基于屏幕空间的,所以我们在采样的时候要把表面坐标从模型空间变换到屏幕坐标空间

相机的深度纹理(左)与光源的阴影映射纹理(右)
基于屏幕空间的阴影图

      那么怎么对阴影图进行采样呢?unity其实已经帮我们封装好了采样的函数。我们可以直接使用三个宏指令,就可以完成对阴影的采样。三个宏指令分别是:SHADOW_COORDS(用在顶点输出结构体内)TRANSFER_SHADOW(用在顶点着色器)SHADOW_ATTENUATION(用在片元着色器)。这三个指令都是在AutoLight.cginc中定义的,所以我们在使用前要记得添加include。

            struct v2f {
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
                //参数为下一个插值寄存器的索引
                SHADOW_COORDS(2)
            };

            v2f vert(a2v v) {
                v2f o;
                //你的定点着色器逻辑
                // Pass shadow coordinates to pixel shader
                TRANSFER_SHADOW(o);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                //片元着色器逻辑
                fixed shadow = SHADOW_ATTENUATION(i);
                //结果计算
            }

      AutoLight中定义的宏指令:

// ---- Screen space direction light shadows helpers (any version)
#if defined (SHADOWS_SCREEN)

    #if defined(UNITY_NO_SCREENSPACE_SHADOWS)
        UNITY_DECLARE_SHADOWMAP(_ShadowMapTexture);
        #define TRANSFER_SHADOW(a) a._ShadowCoord = mul( unity_WorldToShadow[0], mul( unity_ObjectToWorld, v.vertex ) );
        inline fixed unitySampleShadow (unityShadowCoord4 shadowCoord)
        {
            #if defined(SHADOWS_NATIVE)
                fixed shadow = UNITY_SAMPLE_SHADOW(_ShadowMapTexture, shadowCoord.xyz);
                shadow = _LightShadowData.r + shadow * (1-_LightShadowData.r);
                return shadow;
            #else
                unityShadowCoord dist = SAMPLE_DEPTH_TEXTURE(_ShadowMapTexture, shadowCoord.xy);
                // tegra is confused if we use _LightShadowData.x directly
                // with "ambiguous overloaded function reference max(mediump float, float)"
                unityShadowCoord lightShadowDataX = _LightShadowData.x;
                unityShadowCoord threshold = shadowCoord.z;
                return max(dist > threshold, lightShadowDataX);
            #endif
        }

    #else // UNITY_NO_SCREENSPACE_SHADOWS
        UNITY_DECLARE_SCREENSPACE_SHADOWMAP(_ShadowMapTexture);
        #define TRANSFER_SHADOW(a) a._ShadowCoord = ComputeScreenPos(a.pos);
        inline fixed unitySampleShadow (unityShadowCoord4 shadowCoord)
        {
            fixed shadow = UNITY_SAMPLE_SCREEN_SHADOW(_ShadowMapTexture, shadowCoord);
            return shadow;
        }

    #endif

    #define SHADOW_COORDS(idx1) unityShadowCoord4 _ShadowCoord : TEXCOORD##idx1;
    #define SHADOW_ATTENUATION(a) unitySampleShadow(a._ShadowCoord)
#endif

      由于这些宏会使用上下文变量来进行相关计算,我们在编写shader时需要保证自己定义的变量名与宏使用的名称相匹配:a2v结构体(顶点输入结构体)的顶点坐标变量名必须是vertex,顶点着色器中a2v(顶点输入)结构体的名字必须是v,且v2f(顶点输出结构体)的顶点位置必须为pos

5.统一管理阴影与光照衰减

      在片元着色器中我们使用了SHADOW_ATTENUATION宏进行阴影处理(对屏幕空间阴影图进行采样)。unity还封装了一个宏,可以进行统一的光照衰减计算与阴影计算,那就是UNITY_LIGHT_ATTENUATION宏,这个宏需要三个参数,第一个是光照的衰减atten,这个参数我们不用在外部声明,宏内部会自己声明该变量并填充衰减值后传出,第二个参数是我们在片元着色器中拿到的顶点输出结构体,第三个参数是世界空间的坐标,这个坐标用来计算光源空间下的坐标。这个宏针对不同类型的光源和情况声明了多个版本,所以我们在使用的时候不需要在Additional Pass判断光源类型,代码也得以统一。

// UNITY_LIGHT_ATTENUATION not only compute attenuation, but also shadow infos
                UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

总结

      又看了一遍书中关于阴影的部分,确实收益良多。但是还有很多东西现在搞不清楚,比如为什么在生成光源阴影映射纹理的时候要绘制很多次同样的物体,而且不能合批,是不是阴影映射纹理也默认进行了LOD处理呢?

补充:

      在渲染阴影的时候,会绘制四次renderjobdir,这里的阴影也是使用了一种类似于LOD的手法进行处理,生成四份光源空间下的深度图。在游戏运行时根据相机距离来决定最终要采样哪一种质量的阴影贴图,这样可以在游戏执行时动态的优化效率。但是同样付出的代价就是要多绘制几次,也就是多一些DC,同样用来存储的空间也对应的要增大一些。

shadowrender

      那么这个LOD的设置在哪呢?在quality setting中我们可以看到有相关的shadows的设置信息,在这里就可以设置shadow cascades数量啦。

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

推荐阅读更多精彩内容

  • 转载自VR设计云课堂[https://www.jianshu.com/u/c7ffdc4b379e]Unity S...
    水月凡阅读 1,002评论 0 0
  • 我见过世上最美的风景 它是你清澈的眼眸 那泛起的层层涟漪 都是被打碎了的 我的深情 每每有风袭来 请您微闭双眼 轻...
    虚度老太婆阅读 138评论 0 0
  • 一夜喜雨至 满池芍药开 娇颜沾雨露 只为待君来
    喜心阅读 310评论 0 0
  • 一个主题
    pretty1994阅读 223评论 0 0
  • 昨天,同事在办公室说起一名学生,什么都不感兴趣。不学习,不打瞌睡,不偷看课外书,不交友,不玩手机。 整个入定老僧,...
    锦瑟_db50阅读 182评论 0 1