前项渲染和延迟渲染一直懵懵懂懂的,所以希望借此文章让自己更好的理解这两个渲染方式,本文主要参考《Unityshader入门精要》。如果有错误,欢迎指出。图片截图皆来自于《Unityshader入门精要》。
目录
什么是渲染路径?
光照在unityshader中的计算方式(我个人的理解)。
前向渲染路径的原理
每一次前项渲染过程中,我们都需要渲染该对象的图元进行更新深度缓冲和颜色缓冲区的更新。
对于某单个物体进行光照计算的时候,每多一个光源就要多额外增加一个pass,最终这个物体的光照计算结果就是把每个pass中得到的光源计算结果增加起来。
如果有M个光源N个物体就要执行M*N次Pass。这个消耗是非常大的。
光源数量多一点 物体数量多一点直接爆炸,哈哈哈哈
前向渲染路径在Unity里
为了稍微好一点,不那么容易发生爆炸,unity对于光源对于物体的渲染方式做出了适配
方式有逐顶点渲染,逐像素渲染,和SH球谐光照。
unity又根据场景中光源的设置,强度,距离等因素自动对光源做一个排序
然后结果如下:
排序后根据那些光线暗,距离远,效果差的光源就用性能消耗低的渲染方式,亮度高,离物体近的光源就用表现效果号的渲染方式,渲染的方式还能自行设置。
如果光是点光或者聚光,太远或者不在光照范围内,是不算光源的
前向渲染在Unityshader中的框架代码
Shader "RTP/Standard" {
Properties{
}
SubShader{
Tags { "RenderType" = "Opaque" "Queue" = "Geometry"}
Pass {
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
ENDCG
}
Pass {
Tags { "LightMode" = "ForwardAdd" }
Blend One One
CGPROGRAM
#pragma multi_compile_fwdadd
//#pragma multi_compile_fwdadd_fullshadows
ENDCG
}
}
FallBack "Specular"
}
要点分析:
要加pass特有的Tags
BasePass是最基本的得pass,这里计算光照纹理,环境光,自发光,阴影。
AdditionalPass是多光源的pass,默认不支持阴影,但是可以写#pragma multi_compile_fwdadd_fullshadows来代替#pragma multi_compile_fwdadd后就支持阴影了。
#pragma multi_compile_fwdbase和#pragma multi_compile_fwdadd要加上,好像是会影响什么光照衰减,加就对了
环境光和自发光在BasePass中计算一次就行了
AdditionalPass中要加混合模式Blend One One,多pass肯定要混合,不加直接替换了
一般定一个BasePass(除非双面渲染)和一个AddtionalPass就可以了,多光源会多次调用AddtionalPass
AddtionalPass对于不同种类光源的处理
如果只存在平行光,那么直接复制BasePass中的代码,然后去掉环境光,自发光即可,但是unity中有不同的光源种类,光照时光源的信息是不同的,所以为了区分,在AddtionalPass里面加了宏来判断。
是否为平行光,并确定光源到顶点的方向
#ifdef USING_DIRECTIONAL_LIGHT
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
#endif
是否为平行光,并确定光源的衰减,unity通过采样渐变纹理来进行光照衰减,因为计算光照衰减运算量要比采样纹理大(关于光照衰减这部分我就直接写在最后面吧,感觉写在这里不是很合适)
#ifdef USING_DIRECTIONAL_LIGHT
fixed atten = 1.0;
#else
#if defined (POINT)
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
fixedatten=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
#endif
延迟渲染路径
延迟渲染路径包含两个Pass,第一个pass中不计算光照,但获取计算光照需要的很多信息,比如法线信息,视角方向,漫反射系数等,然后再放入缓冲区中。在第二个pass中,我们把这些获取的很多信息拿出来进行光照的计算。
这些可以知道延迟渲染对于光源的数量和场景物体的数量关系并不是很大的,而是和屏幕空间的大小有关,因为屏幕大小和缓冲区的大小有关。(感觉这里说的有点问题,问题不大,汗)。
那这样的话,延迟渲染中每个光源都可以以最高待遇逐像素进行计算了,美汁汁。
但是呢,有好处肯定也有缺点:
不支持真正的抗锯齿(我也不知道为什么)
不能处理半透明物体(我也不知道为什么)
对显卡有一定的要求,有些显卡不支持也没有办法
光照衰减
由于光照衰减的计算量很大,所以采用了纹理采样阴影的计算方法,但是呢,有缺点:
需要预处理
纹理大小影响精度
不直观,不方便,把数据存在表中的话无法用其他公式来计算衰减
UNITY_LIGHT_ATTENUATION(atten, i, worldPos);
这个宏能搞定光照衰减和阴影值相乘的所有问题,不用再担心不同光源的衰减问题了,也就不用加判断点光源还是聚光灯等不同光源效果的宏了
这里贴上《UnityShader入门精要》里面的标准光照着色器,用的blinn-Phong模型
Shader "Unity Shaders Book/Common/Bumped Specular" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_BumpMap ("Normal Map", 2D) = "bump" {}
_Specular ("Specular Color", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float4 TtoW0 : TEXCOORD1;
float4 TtoW1 : TEXCOORD2;
float4 TtoW2 : TEXCOORD3;
SHADOW_COORDS(4)
};
v2f vert(a2v v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
TANGENT_SPACE_ROTATION;
float3 worldPos = mul(_Object2World, v.vertex).xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
fixed3 halfDir = normalize(lightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
UNITY_LIGHT_ATTENUATION(atten, i, worldPos);
return fixed4(ambient + (diffuse + specular) * atten, 1.0);
}
ENDCG
}
Pass {
Tags { "LightMode"="ForwardAdd" }
Blend One One
CGPROGRAM
#pragma multi_compile_fwdadd
// Use the line below to add shadows for point and spot lights
// #pragma multi_compile_fwdadd_fullshadows
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float4 TtoW0 : TEXCOORD1;
float4 TtoW1 : TEXCOORD2;
float4 TtoW2 : TEXCOORD3;
SHADOW_COORDS(4)
};
v2f vert(a2v v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
float3 worldPos = mul(_Object2World, v.vertex).xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
fixed3 halfDir = normalize(lightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
UNITY_LIGHT_ATTENUATION(atten, i, worldPos);
return fixed4((diffuse + specular) * atten, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
就这样了
后续再补充