(一)渲染路径

unity的渲染路径主要分为三个:前向渲染、延迟渲染、以及顶点照明渲染,最后一个顶点照明渲染,性能最好,可遭不住效果不得行,应该被废置了。

这些渲染路径都可以在摄像机的面板里调节的。

当然,渲染路径也不止这些。

那么,为什么我们要特意选择渲染路径呢?选择了对应路径,unity才会按对应路径的方式,帮你填充好一些后续需要用到的向量。

(二)前向渲染

唔姆,从前向渲染开讲。叫成前向渲染,确实有点吓人,但其实就是我们最经常用的那个。说那个,xdm肯定就懂了,不懂也没事,我把这种渲染当做空气般自然。

看看它的伪代码吧,光看伪代码还是感觉很简单的(doge)

在前向渲染中,最重要的就是深度缓冲以及颜色缓冲,对于一个在多逐像素光源的物体,要调用多次这个shader,要是这样的物体和光源一多,性能就有点痛苦了。

所以前向渲染也不会只有一个逐像素处理,还有逐顶点处理,以及一个球谐处理(SH)

唔,去查了一下球谐函数是什么,有点太复杂了,哪天我单独出一节看看吧。

在前向渲染中,Unity会根据场景中各个光源的设置以及这些光源对物体的影响程度(比如,距离该物体的远近、光源强度等)对这些光源进行一个重要度排序。其中每个顶点最多计算4个逐顶点光照。其他的光以球谐函数(SH)的形式计算。这样处理,虽然速度更快,但其实是一个近似的值Unity使用的判断规则如下:

1-渲染模式被设置成Not Important的光源,会按逐顶点或者SH处理

2-场景中的最亮的平行光总是按逐像素处理的

3-渲染模式被设置成Important的光源会按照逐像素处理

4-若根据以上规则得到的逐像素光源少于Quality Setting的逐像素光源数量(Pixel Light Count),则会有更多的光源以逐像素的方式进行渲染

上面这种图挺好的,可以看出unity是怎么自动设置光源是否important的,并以此做不同的处理,就是不太懂,为什么D和G会被重复?

先不管上面的,来看看具体怎么实现前向渲染。

前向渲染有两种pass:base pass和additional pass,两种分工有不同啦。base pass主要注重一次性算完的,additional pass会被别的逐像素光源调用。

在前向渲染里内置的一些变量和函数,下面的_LightMatrix0已经被unity_WorldToLight替代了,我后面再看看有没有别的更新。

(三)延迟渲染

前向渲染的问题是:当场景中包含大量实时光源时,前向渲染的性能会急速下降。

但是延迟渲染可以有效地避免这个问题,它与前向渲染的不同之处是,除了前向渲染中使用的颜色缓冲和深度缓冲之外,延迟渲染还会利用额外的缓冲区,即G缓冲,其中G是英文Geometry的缩写。G缓冲存储了我们所关心的表面(通常是离摄像机最近的表面)的其他信息,例如法线、位置、用于光照计算的材质属性等。

延迟渲染的主要包含两个Pass。在第一个Pass中,不进行任何光照计算,而是仅仅计算哪些片元可见,这主要通过深度缓冲技术实现,当发现一个片元可见,就将其相关信息存储到G缓冲区中。然后,在第二个Pass中,利用G缓冲的各个片元信息,如表面法线、视角方向、漫反射系数等,进行真正的光照计算。伪代码描述如下:

Pass 1 {
	//第一个Pass不进行光照计算,仅存储光照计算所需信息到G缓冲中
	
	for (each primitive in this model) {
 		for (each fragment covered by this primitive) {
 			if (failed in depth test) {
 				discard;//若没有通过深度测试,则该片元不可见
 			}
 			else {
 				//若该片元可见,则进行光照计算,则将相关信息存储到G缓冲中
 				writeBuffer(materialInfo, pos, normal, lightDir, viewDir);
 			}
 		}
	}
}
Pass 2 {
	//利用G缓冲中的信息进行真正的光照计算
	
	for (each pixel in the screen) {
		if (the pixel is valid) {
        	//若该像素有效,则读取其G缓冲中的信息
        	readBuffer(pixel, materialInfo, pos, normal, lightDir, viewDir);
        	
        	//根据读取到的信息进行光照计算
        	float4 color = Shading(materialInfo, pos, normal, lightDir, viewDir);
        	//更新帧缓冲
        	writeFrameBuffer(pixel, color);
                }
        }
}

可以看出,延迟渲染使用的Pass数通常是两个,而与场景中包含的光源数目无关。

第一个Pass(G-Buffer pass)

不进行任何光照计算,只计算哪些是可见的,有点像透明中的操作;

将每个物体对象渲染1次 。在这个pass中,物体对象的漫反射和高光反射的颜色、表面平滑度、世界空间的法线以及自发光+环境光+反射+光照贴图等信息被渲染到屏幕空间的G缓冲中。将G缓冲纹理设置为全局着色器属性,以供以后由着色器( CameraGBufferTexture0 .. CameraGBufferTexture3 names )访问。

第二个Pass(Lighting pass)

主要进行光照计算;

基于G缓冲区和深度来计算光照 。 由于光照是在屏幕空间中计算的,因此光照处理所需的时间与场景的复杂性无关。 最终的光照颜色被存储到帧缓冲中。

未穿过相机近平面(near plane)的点(point)光源和聚光灯(spot)光源被渲染为3D形状,并启用了针对场景的Z缓冲区测试。这使得部分或完全遮挡的点光源和聚光灯光源的渲染代价很低。穿过近平面的平行光(directional lights),点(point)光源和聚光灯(spot)光源被渲染为全屏四边形(quads)。

(四)forwardRendering 实践

这里主要就是把注意事项写一下了。

要注意的还是挺多的,真的不能再打错字了!!!!

打错字真的难debug,总之先看代码吧。

Shader "Unity Study/Chapter9/ForwardRendering"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color("Color Tint",COLOR)=(1,1,1,1)
        _Specular("Specular",COLOR)=(1,1,1,1)
        _Gloss("Gloss",Range(8,256))=20
    }
    SubShader
    {

        Pass
        {
            Tags{"LightMode"="ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            //重点
            #pragma multi_compile_fwdbase

            #include "UnityCG.cginc"
            #include "UnityLightingCommon.cginc" 
            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
                float3 worldNormal:TEXCOORD0;
                float worldPos:TEXCOORD1;
                float2 uv : TEXCOORD2;
            };


            float4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _Specular;
            float _Gloss;

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
                o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float3 worldNormal=normalize(i.worldNormal);
                fixed3 worldLightDir=normalize(UnityWorldSpaceLightDir(i.worldPos));

                fixed3 albedo=tex2D(_MainTex,i.uv).rgb*_Color.rgb;
                fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz*albedo;
                fixed3 diffuse=_LightColor0.rgb*albedo*max(0,dot(worldNormal,worldLightDir));

                fixed3 viewDir=normalize(UnityWorldSpaceViewDir(i.worldPos));
                fixed3 halfDir=normalize(worldLightDir+viewDir);
                fixed3 specular=_LightColor0.rgb*_Specular.rgb*pow(max(0,dot(worldNormal,halfDir)) , _Gloss);

                fixed atten=1.0;
                return fixed4(ambient+(diffuse+specular)*atten,1);
            }
            ENDCG
        }

        Pass
        {
            Tags{"LightMode"="ForwardAdd"}
            Blend One One
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            //重点
            #pragma multi_compile_fwdadd     //这两个前面都一样的multi_complie_fwd

            #include "Lighting.cginc"
            #include "AutoLight.cginc"
            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
                float3 worldNormal:TEXCOORD0;
                float3 worldPos:TEXCOORD1;
                float2 uv : TEXCOORD2;
                SHADOW_COORDS(3)
            };


            float4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _Specular;
            float _Gloss;

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
                o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
                TRANSFER_SHADOW(o);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float3 worldNormal=normalize(i.worldNormal);

                //这里Additional pass要根据不同的光类型给到不同的光的方向,ez
                //这里用#ifdef #else #endif 实现,要记住!!!!!
                #ifdef USING_DIRECTIONAL_LIGHT
                    fixed3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz);
                #else 
                    fixed3 worldLightDir=normalize(_WorldSpaceLightPos0-i.worldPos);
                #endif


                fixed3 albedo=tex2D(_MainTex,i.uv).rgb*_Color.rgb;
                fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz*albedo;
                fixed3 diffuse=_LightColor0.rgb*albedo*max(0,dot(worldNormal.xyz,worldLightDir.xyz));

                fixed3 viewDir=normalize(UnityWorldSpaceViewDir(i.worldPos));
                fixed3 halfDir=normalize(worldLightDir+viewDir);
                fixed3 specular=_LightColor0.rgb*_Specular.rgb*pow(max(0,dot(worldNormal,halfDir)) , _Gloss);

                //计算衰减系数,但不是用传统方法,这里选择用了一张纹理
                //坑啊,还得看网上,书上太老了
                #if defined (POINT)
                    float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
                    fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
                #elif defined (SPOT)
                    float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
                    fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
                #else
                    fixed atten = 1.0;
                #endif

                fixed shadow =SHADOW_ATTENUATION(i);
                return fixed4(ambient+(diffuse+specular)*atten*shadow,1);
            }
            ENDCG
        
        }
    }
    FallBack "Specular"
}

踩过的坑如下:

#pragma multi_compile_fwdadd //这边后面是fwd!!!!


Tags{“LightMode”=”ForwardAdd”} //这里是forward!forward!forward!


unity_WorldToLight替代了_LightMartix0,差点弄得我以为见鬼了


这段代码重要的还是addtional pass的内容,里面有对不同种类光源的判断和以此为依据的一些计算。
还有additional pass的Blend One One不然就直接覆盖掉base了
这里的fixed atten =tex2D(_LightTexture0,dot(lightCoord,lightCoord).rr).UNITY_ATTEN_CHANNEL;还是有点没看懂,主要是dot(lightCoord,lightCoord)以及后面的rr。
我到外面找到了一些解释,还是挺好懂的
dot(lightCoord, lightCoord).rr一句,首先是由点积得到光源的距离平方,这是一个标量,我们对这个变量进行.rr操作相当于构建了一个二维矢量,这个二维矢量每个分量的值都是这个标量值,由此得到一个二维采样坐标。


当然还要记得shadow三剑客,SHADOW_COORDS,TRANSFER_SHADOW,SHADOW_ATTENUATION,可以在AutoLight.cginc里找到。
要使用这些宏,也是有代价的。
像是appdata(a2v)里要用vertex,v2f里要用pos等等,玄学报错的时候,就想想这里有没有什么写错了吧。


后面还有对透明效果的物体也进行投影。但其实还是shadow三件套或者最后一个直接用UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
再设置好对应的FallBack就好了。我就不多贴代码啦。
第九章到此结束!!

您必须 登录 才能发表评论