自己实现PBS

Posted 就当笔记吧

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自己实现PBS相关的知识,希望对你有一定的参考价值。

PBS的全称为Physically Based Shading,就是基于物理的渲染技术

 

1) 为什么需要PBS?

因为PBS渲染出来的效果更好。

 

2) PBS复杂吗?

理论很复杂,但是应用其实也是套用公式,和传统光照不同的公式。

 

3) PBS的公式是怎样的?

a) 只考虑反射的情况,反射颜色=漫反射+镜面反射,没错这里和传统光照是一样的。

b) 漫反射公式:kd为漫反射系数,除以PI是保证能量守恒的

 c) 镜面反射公式,ks一般为1-kd

 

4) 镜面反射中的D, F, G函数,以及微平面理论。 

D: 法线分布函数, 用于拟算多少比例的微面元能把光线镜面反射到我们的眼睛中

微观层面,所有平面都不是光滑的,由许多微面元组成,也就是表面凹凸不平,这也就造成了入射光线会向各个方向反射,只有反射方向和观察方向重叠的(即微平面法线和半角向量重叠),才能进入到我们的眼睛。

所以,入射光线经过微面元镜面反射后,只有一定比例的光线能进入我们的眼睛。

 

G: 阴影遮掩函数, 用于拟算多少比例的微面元反射被遮掉

微观层面,反射的时候,又遇到突起很高的微面元被挡住了;或者其他情况的遮挡。

 

F: 菲涅尔函数,用于拟算镜面反射比例。

就是达到掠射角大,反射弱;掠射角小,反射强这样的效果。

 

最终效果

 

实际写shader时,因为出于实际效果等因素,并不是完全按照上面的理论公式套用的,部分做了调整

Shader "My/PBS/MyPBS"

    Properties
    
        _MainTex("Texture", 2D) = "white" 
        _Color("Tint Color", Color) = (1, 1, 1, 1)

        _Metallic("Metallic", Range(0, 1)) = 0 //金属度要经过伽马校正
        _Smoothness("Smoothness", Range(0, 1)) = 0.5
    
    SubShader
    
        Tags  "RenderType" = "Opaque" 
        LOD 100

        Pass
        
            Tags  "LightMode" = "ForwardBase" 

            CGPROGRAM

            #pragma multi_compile_fwdbase
            //#pragma target 3.0
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "UnityStandardBRDF.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;
            half4 _Color;

            half _Metallic;
            half _Smoothness;

            struct appdata
            
                float4 vertex : POSITION;
                float4 texcoord : TEXCOORD0;
                float3 normal : NORMAL; //顶点法线
            ;

            struct v2f
            
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
                float3 worldNormal : TEXCOORD2;
                float3 worldViewDir : TEXCOORD3;
            ;

            //G (Geometry function)
            float GeometrySchlickGGX(float NdotV, float k)
            
                float nom = NdotV;
                float denom = NdotV * (1.0 - k) + k;
                return nom / denom;
            

            float GeometrySmith(float NdotV, float NdotL, float Roughness)
            
                float squareRoughness = Roughness * Roughness;
                float k = pow(squareRoughness + 1, 2) / 8;
                float ggx1 = GeometrySchlickGGX(NdotV, k); // 视线方向的几何遮挡
                float ggx2 = GeometrySchlickGGX(NdotL, k); // 光线方向的几何阴影
                return ggx1 * ggx2;
            

            //近似的菲涅尔函数
            float3 FresnelSchlick(float3 F0, float VdotH)
            
                float3 F = F0 + (1 - F0) * exp2((-5.55473 * VdotH - 6.98316) * VdotH);
                return F;
            

            v2f vert(appdata v)
            
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal); //法线(世界空间)
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; //世界坐标
                o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos); //观看方向(世界空间), 顶点指向观察点

                return o;
            

            fixed4 frag(v2f i) : SV_Target
            
                float3 worldNormal = normalize(i.worldNormal); //法线
                float3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); //光源入射方向
                float3 worldViewDir = normalize(i.worldViewDir); //观看方向
                float3 worldHalfDir = normalize(worldLightDir + worldViewDir); //半角向量

                float NdotV = max(saturate(dot(worldNormal, worldViewDir)), 1e-5); //防止除0
                float NdotL = max(saturate(dot(worldNormal, worldLightDir)), 1e-5);
                //float LdotH = max(saturate(dot(worldLightDir, worldHalfDir)), 1e-5);
                float NdotH = max(saturate(dot(worldNormal, worldHalfDir)), 1e-5);
                float VdotH = max(saturate(dot(worldViewDir, worldHalfDir)), 1e-5);

                float perceptualRoughness = 1 - _Smoothness;
                float roughness = PerceptualRoughnessToRoughness(perceptualRoughness); //粗糙度
                roughness = max(roughness, 0.002); //防止为0,保留一点点高光

                half3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb; //反射率, 我们看到的颜色就是没被吸收的剩余颜色

                //直接光镜面反射相关
                half D = GGXTerm(NdotH, roughness); //法线分布函数, 用于拟算多少比例的微面元的法线与半角向量重叠
                half G = GeometrySmith(NdotV, NdotL, roughness); //阴影遮掩函数, 用于拟算N=H的微面元中, 有多少比例会被遮挡掉
                half3 F0 = lerp(unity_ColorSpaceDielectricSpec.rgb, albedo, _Metallic); //unity_ColorSpaceDielectricSpec为常数: float3(0.04, 0.04, 0.04)
                half3 F = FresnelSchlick(F0, VdotH); //近似的菲涅尔函数(菲涅尔效应)
                half3 specular = (D * G * F * 0.25) / (NdotV * NdotL) * _LightColor0.rgb * NdotL * UNITY_PI;

                //直接光漫反射
                //half3 kd = (1 - F)*(1 - metallic); //漫反射系数,公式上更遵循物理,但效果上没有内置宏好
                half kd = OneMinusReflectivityFromMetallic(_Metallic); //漫反射系数,内置宏
                half3 diffuse = kd * albedo * _LightColor0.rgb * NdotL;

                return fixed4(diffuse + specular, 1);
            

            ENDCG
        
    

 

参考

 

CIR,CBS,EBS,PIR,PBS 名词解释 令牌桶应用

为了达到上述目的,我们需要对进入网络的流量进行监督,实现CAR(Committed Access Rate)。

CAR:将进入网络的用户流量的速率限制在约定的范围之内,从而避免引起网络拥塞。

CIR(Committed Information Rate):承诺信息速率,表示向C桶(单桶模式中只有一个令牌桶,称为C桶)中投放令牌的速率,即C桶允许传输或转发报文的平均速率。

CBS(Committed Burst Size):承诺突发尺寸,表示C桶的容量,即C桶瞬间能够通过的承诺突发流量。相当于盛放通行卡的盒子里最多可以放多少张通行卡,也就是说,该自动出卡系统允许通过的车队中最多可以有多少辆车。

EBS(Excess Burst Size):超额突发尺寸,表示E桶的容量,即E桶瞬间能够通过的超出突发流量。

PIR(Peak information rate):峰值信息速率,表示向P桶中投放令牌的速率,即P桶允许传输或转发报文的峰值速率。PIR的值应大于CIR(存在服务卡足够而通行卡不够的情况)。

PBS(Peak Burst Size):峰值突发尺寸,表示P桶的容量,即P桶瞬间能够通过的峰值突发流量。

说明:目前华为以太网交换机只支持单速单桶和双速双桶模式。

简单来说,带宽参数的设置取决于实际业务的限速需要。原则上,令牌桶容量需要大于等于网络中可能出现的最大的报的长度和业务流量的正常突发量。对于华为的以太网交换机,我们有总结的经验性公式:

l  带宽 ≤ 100Mbit/s时,令牌桶容量(Bytes) = 带宽(kbit/s) * 1000(s)/8

l  带宽 > 100Mbit/s时,令牌桶容量(Bytes) = 100000(kbit/s) * 1000 (s)/8

另外,华为的以太网交换机,不同系列的单板支持的CAR粒度不同。在进行流量监管和限速时,

l  如果配置的CIR、PIR是最小粒度的整数倍,则按照配置的速率进行监管和限速。

l  如果配置的CIR、PIR不是最小粒度的整数倍,则按照最小粒度的整数倍进行流量监管和限速。

例如,设备上的监管和限速粒度为64kbit/s,如果CIR值 ≤ 64kbit/s,按照64kbit/s处理;如果128kbit/s < CIR值 ≤ 192kbit/s,按照192kbit/s处理。

令牌桶原理应用之接口限速

令牌桶原理可以应用到设备的入方向和出方向。根据令牌桶原理在不同方向的应用,可以实现不同的功能,见下图。

技术分享图片

流量监管、流量整形、接口限速与令牌桶算法之间的关系,见下表。

应用方向

单速单桶

双速双桶

设备入方向 基于接口,实现接口限速 基于流,实现流量监管
设备出方向 基于接口,实现接口限速 基于队列,实现流量整形

流量监管是一种通过对流量规格进行监督,以限制流量及网络资源使用的流控策略。如果这种流控策略应用到设备接口的入方向,也可以实现入方向的接口限速。与基于接口实现的入方向的接口限速相比,这种方式引入了MQC(Modular QoS Command-Line Interface),因此应用更加灵活。

通常我们所说的限速是广义上的接口限速,包括基于接口和基于MQC实现的入方向的接口限速。

以上是关于自己实现PBS的主要内容,如果未能解决你的问题,请参考以下文章

Unity5 GI与PBS渲染从用法到着色代码

PBS Torque 5.1.3安装配置

如何与多个用户环境中运行的PBS

CIR,CBS,EBS,PIR,PBS 名词解释 令牌桶应用

Thinking in Unity3D:基于物理着色(PBS)的材质系统

调度器PBS