PBR实现

我们的目标:UnityStandard

我一直作Unity开发,所以环境也就选择了Unity。目标也就是尽可能接近UnityStandard的效果,不过主要为了学习PBR,所以就只实现基本的PBR和法线。也就是使用Albedo,Matellic,Normal三个贴图。遮蔽,自发光,反射和ImageBasedLighting这种就先不管了

PBR原理

PBR的原理务必要看Trace的这篇【PBR】基于物理渲染的基础理论1.0版

简单的解释
1. 分开处理反射面的绝缘体特性和金属特性,最后光照应该是Diffuse+Specular
2. 纯金属没有Diffuse,非金属主要是Diffuse,有一点反射
3. 反光部分的主要有三个东西影响:微表面的法线分布(NDF),微表面的入射和反射遮挡(Geometry Function),反射率和入射角的关系(Fresnel反射)

猜测Unity的方案

我没去找Standard的源码,照着WORKING WITH PHYSICALLY-BASED SHADING: A PRACTICAL APPROACH猜测一下他的方案。

Unity Metallic方式

1. Albedo:基础色,和以前的Diffuse图不一样,不能画出光的效
2. Metallic:Metallic图的r通道,描述金属性质。应该是描述反射率的参数,用这个参数将Diffuse部分和Specular部分分开
3. Smoothness:光滑度,Metallic图的a通道。用来描述光滑程度(废话),或者说,描述微表面理论中微表面法线和整体法线的一致程度(NDF),以及微表面遮挡的程度(Geometry Function)
4. Normal:法线,没啥说的

所以看起来差不多应该是这样的意思:
Diffuse*(1-Metallic)+f(l,v)*Metallic

f(l,v)就是PBR的核心内容,BRDF公式:

//       D(h) F(v,h) G(l,v,h)
//f(l,v) = ---------------------------
//       4(n·l)(n·v)

BRDF公式的选择与说明

公式这东西谁看谁晕,所以先强推这个文章
如何看懂这些"该死的"图形学公式
我能继续搞下去全靠这个了。

到这里问题就很简单了,选择合适的公式就对了。翻找了一下SIGGRAPH的文章,全是干货真是好地方!照着人家的干货做了如下选择:

1. D(h):GGX

//	  alpha^2
//D(m) = -----------------------------------
//	  pi*((n·m)^2 *(alpha^2-1)+1)^2

Q:alpha是什么
A:alpha = roughness * roughness,roughness是粗糙度,roughness= 1-smoothness

Q:m是什么?h是什么?
A:m是微表面的法线, h是入射光和反射光的半角向量,根据微表面原理,只有法线和半角向量一致的微表面参与反射,所以m和h是相等的

2. G(l,v,h):Smith-Schlick,在Smith近似下G(l,v,h) = g(l)*g(v),讲道理入射和反射的遮蔽应该是相同的。

//	  n·v
//g(v) = -----------------
//     (n·v) *(1-k) +k

  

Q:k是什么?
A:k是Schlick公式的参数,具体的要去看Schlick的论文

Q:k应该是多少?
A:我看了几个地方,k的选择都不太一样,这里我们本着找NB的抄的态度,用了UE4的 k =(a^2 +1) * (a^2 +1)/8;

3. F(v,h):UE4对Schlick的一个近似。

//Schlick
//F(v,h) = F0 +(1-F0)*(1-(v·h))^5
//
//UE4 approximation
//
//F(v,h) = F0+(1-F0)2^((-5.55473(v·h)-6.98316)*v·h)

  

Q:F0是什么?
A:F0,入射角0时的反射率。这个数值我用了Metallic的值,应该还有跟roughness相关的计算,谁清除求告知

然后合成一个!

//       alpha^2 * (F0+(1-F0)*pow(2,(-5.55473(v·h)-6.98316)*v·h))
//f(l,v) = ---------------------------------------------------------------------------
//       4*pi*((n·h)^2 *(alpha^2-1)+1)^2*((n·v) *(1-k) +k)*((n·l) *(1-k) +k)

  

写Shader

这就没啥说的了,就是把公式写上去。
效果图:右边是Standard,左边是Custom,有一点点不同,反射更强烈一点

Shader "Stein/CustomPBR"
{
    Properties
    {
        _Matel("Matel",2D) = "white"{}
        _Albedo("Albedo", 2D) = "white" {}
          _Normal ("Normal", 2D) = "bump"{}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            Name "FORWARD"
            Tags {
                "LightMode"="ForwardBase"
            }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
            #pragma multi_compile_fwdbase_fullshadows

            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #define PI 3.14159265359

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal:NORMAL;
                 float4 tangent : TANGENT;
            };

            struct VertexOutput
            {
                float4 pos : SV_POSITION;
                float2 uv0 : TEXCOORD0;
                float4 posWorld : TEXCOORD1;
                float3 normalDir : TEXCOORD2;
                float3 tangentDir : TEXCOORD3;
                float3 bitangentDir : TEXCOORD4;
                LIGHTING_COORDS(5,6)
                UNITY_FOG_COORDS(7)
            };

            uniform float4 _LightColor0;

            sampler2D _Albedo;
            float4 _Albedo_ST;
            sampler2D _Matel;
            float4 _Matel_ST;
            uniform sampler2D _Normal;
            uniform float4 _Normal_ST;

            VertexOutput vert (appdata v)
            {
                 VertexOutput o = (VertexOutput)0;

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex );
                o.uv0 = v.uv;
                o.posWorld = mul(_Object2World, v.vertex);

                //世界坐标下的几个向量值,参考ShaderForge
                o.normalDir = UnityObjectToWorldNormal(v.normal);
                o.tangentDir = normalize( mul( _Object2World, float4( v.tangent.xyz, 0.0 ) ).xyz );
                o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w);

                UNITY_TRANSFER_FOG(o,o.pos);
                TRANSFER_VERTEX_TO_FRAGMENT(o)
                return o;
            }

            fixed4 frag (VertexOutput i) : SV_Target
            {
                 i.normalDir = normalize(i.normalDir);

                float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
                float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);

                //法线左边转换
                float3x3 tangentTransform = float3x3( i.tangentDir, i.bitangentDir, i.normalDir);//法线的TBN旋转矩阵
                float4 _Normal_var = tex2D(_Normal,TRANSFORM_TEX(i.uv0, _Normal));
                float3 normalLocal =_Normal_var.rgb*2-1;//之前的问题是没有Unpack,整个坐标是偏了的,参考UnityCG.cginc
                float3 normalDirection = normalize(mul( normalLocal, tangentTransform )); // 最终的法线

                //从matellic图上取数据
                fixed4 matelTex = tex2D(_Matel,TRANSFORM_TEX(i.uv0,_Matel));
                float matellic = matelTex.r;//unity matellic 值,是一个grayscale value ,存在 r 通道
                float roughness = 1-matelTex.a;//unity 用的是smoothness,在matellic map的alpha 通道,这里转换一下
                float f0 = matelTex.r;//HACK 这个就是先这样用……

                //预先计算一些常量
                float3 h =normalize( lightDirection+viewDirection);//h,l和v的半角向量
                float a = roughness*roughness;//alpha
                float a2 = a*a;//alpha^2

                float NoL =saturate( dot(normalDirection,lightDirection));
                float NoV =saturate(dot(normalDirection,viewDirection));
                float NoH =saturate(dot(normalDirection,h));
                float VoH =saturate(dot(viewDirection,h));

                //light & light color
                float3 attenColor = LIGHT_ATTENUATION(i) * _LightColor0.xyz;

                // sample the _Albedo texture
                fixed4 albedo = tex2D(_Albedo, i.uv0);

                //diffuse part
                float3 directDiffuse =dot( normalDirection, lightDirection ) * attenColor;
                float3 indirectDiffuse = float3(0,0,0);
                indirectDiffuse += UNITY_LIGHTMODEL_AMBIENT.rgb; // Ambient Light
                float3 diffuse = (directDiffuse + indirectDiffuse) * albedo*(1-matellic);

                //specular part
                //微表面BRDF公式
                //                D(h) F(v,h) G(l,v,h)
                //f(l,v) = ---------------------------
                //                4(n·l)(n·v)

                //这个是GGX
                //                alpha^2
                //D(m) = -----------------------------------
                //                pi*((n·m)^2 *(alpha^2-1)+1)^2

                //简化 D(h)*PI/4
                float sqrtD = rcp(NoH*NoH*(a2-1)+1);
//                float D = a2*sqrtD*sqrtD/rcp(PI*4);
                float D = a2*sqrtD*sqrtD/4;//在 direct specular时,BRDF好像要乘PI,这里就直接约去。Naty Hoffman的那个文没太看懂

                //in smith model G(l,v,h) = g(l)*g(v),这个公式是Schlick的趋近公式,参数各有不同
                //                n·v
                //G(v) = -----------------
                //                (n·v) *(1-k) +k

//                float k = a2*sqrt(2/PI);             //Schlick-Beckmann
//                float k = a2/2;                        //Schlick-GGX
                float k =(a2+1)*(a2+1)/8;        //UE4,咱们就挑NB的抄

                //简化G(l,v,h)/(n·l)(n·v)
                float GV=(NoV *(1-k) +k);
                float GL =(NoL *(1-k) +k);

                //F(v,h)
                float f = f0 +(1-f0)*pow(2,(-5.55473*VoH-6.98316)*VoH);//参数是从UE4那里抄来的,应该是Schlick公式的趋近

                fixed3 specularTerm = D*f *rcp(GV*GL);

                fixed3 specular = albedo*attenColor*(1/PI+ specularTerm)*NoL*matellic;//albedo/PI是BRDF公式的diffuse部分,没有就会偏黑
                fixed4 finalcolor = (fixed4)0;
                finalcolor.rgb =diffuse +specular;
                finalcolor.a = albedo.a;

                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, finalcolor);
                return finalcolor;
            }
            ENDCG
        }

        Pass
        {
            Name "FORWARD_DELTA"
            Tags {
                "LightMode"="ForwardAdd"
            }
             Blend One One

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
            #pragma multi_compile_fwdbase_fullshadows

            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #define PI 3.14159265359

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal:NORMAL;
                 float4 tangent : TANGENT;
            };

            struct VertexOutput
            {
                float4 pos : SV_POSITION;
                float2 uv0 : TEXCOORD0;
                float4 posWorld : TEXCOORD1;
                float3 normalDir : TEXCOORD2;
                float3 tangentDir : TEXCOORD3;
                float3 bitangentDir : TEXCOORD4;
                LIGHTING_COORDS(5,6)
                UNITY_FOG_COORDS(7)
            };

            uniform float4 _LightColor0;

            sampler2D _Albedo;
            float4 _Albedo_ST;
            sampler2D _Matel;
            float4 _Matel_ST;
            uniform sampler2D _Normal;
            uniform float4 _Normal_ST;

            VertexOutput vert (appdata v)
            {
                 VertexOutput o = (VertexOutput)0;

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex );
                o.uv0 = v.uv;
                o.posWorld = mul(_Object2World, v.vertex);

                //世界坐标下的几个向量值,参考ShaderForge
                o.normalDir = UnityObjectToWorldNormal(v.normal);
                o.tangentDir = normalize( mul( _Object2World, float4( v.tangent.xyz, 0.0 ) ).xyz );
                o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w);

                UNITY_TRANSFER_FOG(o,o.pos);
                TRANSFER_VERTEX_TO_FRAGMENT(o)
                return o;
            }

            fixed4 frag (VertexOutput i) : SV_Target
            {
                 i.normalDir = normalize(i.normalDir);

                //light dir & light color
                 float3 lightDirection  =(float3)0;
                 float3 attenColor = (float3)0;

                 if(_WorldSpaceLightPos0.w==0)
                 {
                    lightDirection = normalize(_WorldSpaceLightPos0.xyz);
                    attenColor = LIGHT_ATTENUATION(i) * _LightColor0.xyz;
                 }
                 else
                 {
                     lightDirection =_WorldSpaceLightPos0.xyz- i.posWorld;
                     attenColor =_LightColor0.xyz /(1+length(lightDirection));
                     lightDirection = normalize(lightDirection);
                 }

//                float3 attenColor = LIGHT_ATTENUATION(i) * _LightColor0.xyz;

                float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);

                //法线左边转换
                float3x3 tangentTransform = float3x3( i.tangentDir, i.bitangentDir, i.normalDir);//法线的TBN旋转矩阵
                float4 _Normal_var = tex2D(_Normal,TRANSFORM_TEX(i.uv0, _Normal));
                float3 normalLocal =_Normal_var.rgb*2-1;//之前的问题是没有Unpack,整个坐标是偏了的,参考UnityCG.cginc
                float3 normalDirection = normalize(mul( normalLocal, tangentTransform )); // 最终的法线

                //从matellic图上取数据
                fixed4 matelTex = tex2D(_Matel,TRANSFORM_TEX(i.uv0,_Matel));
                float matellic = matelTex.r;//unity matellic 值,是一个grayscale value ,存在 r 通道
                float roughness = 1-matelTex.a;//unity 用的是smoothness,在matellic map的alpha 通道,这里转换一下
                float f0 = matelTex.r;//HACK 这个就是先这样用……

                //预先计算一些常量
                float3 h =normalize( lightDirection+viewDirection);//h,l和v的半角向量
                float a = roughness*roughness;//alpha
                float a2 = a*a;//alpha^2

                float NoL =saturate( dot(normalDirection,lightDirection));
                float NoV =saturate(dot(normalDirection,viewDirection));
                float NoH =saturate(dot(normalDirection,h));
                float VoH =saturate(dot(viewDirection,h));

                // sample the _Albedo texture
                fixed4 albedo = tex2D(_Albedo, i.uv0);

                //diffuse part
                float3 directDiffuse =dot( normalDirection, lightDirection ) * attenColor;
                float3 indirectDiffuse = float3(0,0,0);
                indirectDiffuse += UNITY_LIGHTMODEL_AMBIENT.rgb; // Ambient Light
                float3 diffuse = (directDiffuse + indirectDiffuse) * albedo*(1-matellic);

                //specular part
                //微表面BRDF公式
                //                D(h) F(v,h) G(l,v,h)
                //f(l,v) = ---------------------------
                //                4(n·l)(n·v)

                //这个是GGX
                //                alpha^2
                //D(m) = -----------------------------------
                //                pi*((n·m)^2 *(alpha^2-1)+1)^2

                //简化 D(h)*PI/4
                float sqrtD = rcp(NoH*NoH*(a2-1)+1);
//                float D = a2*sqrtD*sqrtD/rcp(PI*4);
                float D = a2*sqrtD*sqrtD/4;//在 direct specular时,BRDF好像要乘PI,这里就直接约去。Naty Hoffman的那个文没太看懂

                //in smith model G(l,v,h) = g(l)*g(v),这个公式是Schlick的趋近公式,参数各有不同
                //                n·v
                //G(v) = -----------------
                //                (n·v) *(1-k) +k

//                float k = a2*sqrt(2/PI);             //Schlick-Beckmann
//                float k = a2/2;                        //Schlick-GGX
                float k =(a2+1)*(a2+1)/8;        //UE4,咱们就挑NB的抄

                //简化G(l,v,h)/(n·l)(n·v)
                float GV=(NoV *(1-k) +k);
                float GL =(NoL *(1-k) +k);

                //F(v,h)
                float f = f0 +(1-f0)*pow(2,(-5.55473*VoH-6.98316)*VoH);//参数是从UE4那里抄来的,应该是Schlick公式的趋近

                fixed3 specularTerm = D*f *rcp(GV*GL);

                fixed3 specular = albedo* attenColor*(1/PI+ specularTerm)*NoL*matellic;//albedo/PI是BRDF公式的diffuse部分,没有就会偏黑
                fixed4 finalcolor = (fixed4)0;
                finalcolor.rgb =diffuse +specular;
                finalcolor.a = 0;

                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, finalcolor);
                return finalcolor;
            }
            ENDCG
        }
    }
}
时间: 2024-11-08 04:42:56

PBR实现的相关文章

CCNP路由实验之十六 策略路由(PBR)

?? 策略路由(PBR)是一种比基于目标网络进行路由更加灵活的数据包路由转发机制.路由器将通过路由图决定如何对需要路由的数据包进行处理,路由图决定了一个数据包的下一跳转发路由器.在路由器转发一个数据报文时,首先根据配置的规则对报文进行过滤,匹配成功则按照一定的转发策略进行报文转发.这种规则可以是基于标准和扩展访问控制列表,也可以基于报文的长度:而转发策略则是控制报文按照指定的策略路由表进行转发,也可以修改报文的IP优先字段,策略路由也可以在一定程度上实现流量工程,使不同服务质量的流或者不同性质的

记一次企业高级组网中不正确配置PBR引发的环路排错

嗨,各位.好久没了,最近忙的一塌糊涂,作为一个勤奋好学的网工,我要把实战中遇到的问题记录下来,同时分享给各位.此次的文档分享,是上个月的一次项目实战中记录下来的PBR-策略路由排错.具体的配置不会一一记录,但是会写个大概配置. 毕竟都是有几年的网工经验了,基础的不会就别看了,我都嫌弃!!! 不多说,我们先上一张图,图的背景就不多介绍了,反正可以给大家保证的是全部均为实实在在的真机. PS:图进行了和谐,还有很多设备均进行了删减,做网工永远要记住一件事情,信息安全!!!! 图中设备清单如下:(仅列

4路外线(NAT+PBR真实案例)

三路电信企业宽带固定IP.一路联通企业宽带固定IP. 需求:每个二层下组成一个内网.每个内网分配一路外线(暂时不做故障转移)  设备(1台cisco3560三层交换机.一台cisco2911路由器且只有两个端口.4台二层交换机) 数据走向图: 划分5个vlan,其中4个vlan各对应下面4台二层交换机,也就是对应每个内网,第5个vlan用于扩展路由器外线接口(路由器外线接口采用多IP方案,没有用子接口) 路由器内线与三层交换机采用三层互联(内线对应的三层交换机端口是三层端口) 4个二层交换机所对

在Cisco IOS上使用PBR、SLA和EEM实现双线冗余和负载均衡

背景: 随着信息化办公越来越普及,人们办公对互联网的依赖越来越大,IT.网络在企业中的影响也越来越大.因此,企业网络的可靠性变得十分重要.对于一个互联网公司或者使用需要使用网络才能正常办公的公司来说,断网意味着员工不能正常办公,公司大把的金钱损失.在这样一种背景下,大型公司和金融行业早已实现了双线或多线冗余,而一些中心型企业由于预算等问题还忍受着这样的苦恼.本文将为中小型企业提供一套完美的解决方案,通过已有设备或较低的预算即实现双线冗余.负载均衡与自动切换. 先来介绍一下大中型公司双线互联网接入

openstack 使用pbr配置,setup.cfg的格式与含义

pbr - Python Build Reasonableness A library for managing setuptools packaging needs in a consistent manner. pbr reads and then filters the setup.cfg data through a setup hook to fill in default values and provide more sensible behaviors, and then fee

UnrealEngine4 PBR Shading Model 概述

虽然是概述,但内容并还是有些多,写上一篇PBR概念概述后,也在考虑怎么继续下去,最后还是觉得先多写一些东西再慢慢总结,所以还是尽量把这些年PBR相关的Paper精粹沉淀下来吧. 因为UE4开源的缘故,所以一开始还从它入手.相关的ppt和notebook可以从下面的链接下载,同期的黑色行动2(black op2)的PBR使用也是很有参考价值的,加上本文里也有OP2的IBL近似方法的介绍,如果没看过那也很值得下载的. http://blog.selfshadow.com/publications/s

关于电脑启动流程,MBR,PBR的学学习和理解。

最近又在折腾系统,在笔记本上安装了03+WIN8PE的组合,Windows 8 ,Ubuntu 14.04和Kali,后续准备再安装MAC,当然那是以后的事了.硬盘的分区和系统分布情况如下: MBR:grub4dos 主分区1(NTFS,2G,活动):安装真个硬盘上所有系统的引导(grub4dos)和03pe+win8pe,PBR为grub2 主分区2(NTFS,80G):安装Windows 8,PBR为NT6.X的bootmgr引导程序 扩展分区(850G) 逻辑分区3,4,5(NTFS)(2

unity再战PBR材质流程与材质制作实践

这篇在上一篇的基础上增加了对PBR的认识,主要包括了金属度和粗糙度(光滑度)的测试 unity里PBR流程,PBR材质属性具体分析 传统模型到PBR的流程,一些PBR制作转换工具介绍 以前这篇http://blog.csdn.net/shenmifangke/article/details/50587290 先上一张unity Scene场景的实时效果图(材质的贴图来自网络经过修改) 这两种材质都是基于unity自带材质球就是standard 下面就说说这个建立材质球后默认的材质 Unity5中

H3C交换配置PBR最佳实践

简要说明 PBR算是比较常用的功能,需要我们去掌握一下 配置步骤简要 配置BFD 配置track 配置acl 配置policy-based-route 在接口上面应用policy-based-route 配置步骤详解 配置BFD bfd echo-source-ip ip-address 配置track track 10 bfd echo interface Vlan-interface100 remote ip 192.168.10.4 local ip 192.168.10.5 track

基于物理的渲染技术(PBR)系列二

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D实战核心技术详解>电子工业出版社等. CSDN视频网址:http://edu.csdn.net/lecturer/144 继续上篇博客中基于物理的渲染技术(PBR)系列一的讲解,在这里我们引入了一种被称为渲染方程(Render Equation)的东西.它是某些聪明绝顶人所构想出来的一个精妙的方程式,是