(一)渲染路径
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就好了。我就不多贴代码啦。
第九章到此结束!!
本文地址: UNITY SHADER 入门精要——第9章
您必须 登录 才能发表评论