基于物理的渲染算法 有关 Unity3D 的概括研究

基于物理的渲染算法 有关 Unity3D 的概括研究

来说说基于物理的渲染。
基于物理的渲染(PBR)近几年非常火爆。
Unity 5, 虚幻引擎 4, 寒霜引擎, 甚至是 ThreeJS, 更多的游戏引擎在使用它。
很多 3D 模型工作室在根据 Marmoset Toolbag 和 Allegorithmic Substance Suite等人气工具转换成 “PBR Pipeline”。
现在没几个不熟悉管线的美术,但是了解管线在后台是怎么运行的,很少有引擎和美术知道。
我这个指南分解(分析) PBR 渲染 (shading),是想让初学者方便理解 PBR。
让我们开始吧。

PBR的物理特征
在过去的 3 ~ 40 年,我们对周围世界的理解和科学上 / 数学上它是怎么运作的都有很多进步。
而且这种理解在渲染技术领域也有了很大的发展。
多位灵敏的研究者对光,视野,表面正常形态,然后这三个是怎么互相作用的有着深刻的结论。
这种发展大部分是围绕 BRDF (双向反射率分布函数)和固有能量保存的想法为中心形成的。
​如果想理解光和观测点跟Surface是怎么相互作用的,首先要理解Surface本身。
光照射到完全光滑的表面几乎可以在表面形成完整的反射。
光和我们叫做粗糙表面的东西相互作用的时候,他的反射不是类似的,这可以用微表面 (microfacets)的存在说明。
在这里插入图片描述

我们看物件的时候,要假设他的表面不是完全光滑的,而是用很小的面组成的。每个物件都是完整的 Specular reflection 。
这个微表面(microfacets)通过光滑表面的法线,具有分布的法线。
微表面法线的不同程度是根据表面粗糙度定的。
表面越粗糙,高光损失的可能性就越大。
所以粗糙的表面有更大更模糊的斑点。
光滑的表面上,光的反射比之前更完整时可以压缩反射高光。

再回到 BRDF…
双向反射率分布函数 (Bridirectional Reflectance Distribution Function, BRDF)是说明表面反射率的函数。
有各种 BRDF 模型 / 算法,其中大部分不是基于物理的算法。
基于物理的 BRDF需要能量守恒。
'能量守恒 (Energy Conservation)'是说从地表反射的总光量,比地表收到的总量少。
表面反射的光不能比之前讨论的所有微表面相互作用前更强烈。

BRDF 算法的特点是比其他算法更复杂的Shading模型。
这个Shading模型技术上以三个单一部分组成 : 正规分布函数、几何阴影函数和菲涅尔函数。
一起使用这个算法就能理解了。
在这里插入图片描述
要理解 BRDF,重点是理解构成 BRDF 的三种功能。
依次实现并做对我们有帮助的阴影模型。

编写 PBR Shader : nut,bolt 和光滑的表面

PBR Shader 属性

大部分 PBR Shading 模型上通常是用某种形态影响几种相同属性。
现代 PBR 思路上最重要的两种属性是光滑 (Smoothness) 和金属性 (Metallic)。

这两个值都是在 0…1 之间的时候最好起效。
编写 PBR Shader 的方法有多个,其中一部分为了更多类似迪士尼 PBR 管线的效果可以使用 BRDF 模型。各个效果由特定属性引出。

如果在 Unity 里没有细读 Writing Shaders相关我的页面,这可以成为你可以集中细读的时间。

Shader "Physically-Based-Lighting" {
    Properties { 
    _Color ("Main Color", Color) = (1,1,1,1)                    //diffuse Color
    _SpecularColor ("Specular Color", Color) = (1,1,1,1)        //Specular Color (Not Used)
    _Glossiness("Smoothness",Range(0,1)) = 1                    //My Smoothness
    _Metallic("Metalness",Range(0,1)) = 0                    //My Metal Value      

    }
    SubShader {
	Tags {
            "RenderType"="Opaque"  "Queue"="Geometry"
        } 
        Pass {
            Name "FORWARD"
            Tags {
                "LightMode"="ForwardBase"
            }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #define UNITY_PASS_FORWARDBASE
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #include "Lighting.cginc"
            #pragma multi_compile_fwdbase_fullshadows      
            #pragma target 3.0

float4 _Color;
float4 _SpecularColor;
float _Glossiness;
float _Metallic;

在 Unity Shader 里定义 public 变数。那些以后会添加。
属性下方有Shader初始化结构。
以后添加更多功能的时候会参考 #pragma 指令。

Vertex Program

Vertex 程序跟 Unity 里的编写 Shade r相关自学书上生成的类似。
我们需要的核心因素是有关 vertex 的 normal, tangent 和 bitangent 信息。
也就是说这些一定要包含在 Vertex Program里。

struct VertexInput {
    float4 vertex : POSITION;       //local vertex position
    float3 normal : NORMAL;         //normal direction
    float4 tangent : TANGENT;       //tangent direction    
    float2 texcoord0 : TEXCOORD0;   //uv coordinates
    float2 texcoord1 : TEXCOORD1;   //lightmap uv coordinates
};

struct VertexOutput {
    float4 pos : SV_POSITION;              //screen clip space position and depth
    float2 uv0 : TEXCOORD0;                //uv coordinates
    float2 uv1 : TEXCOORD1;                //lightmap uv coordinates

//below we create our own variables with the texcoord semantic. 
    float3 normalDir : TEXCOORD3;          //normal direction   
    float3 posWorld : TEXCOORD4;          //normal direction   
    float3 tangentDir : TEXCOORD5;
    float3 bitangentDir : TEXCOORD6;
    LIGHTING_COORDS(7,8)                   //this initializes the unity lighting and shadow
    UNITY_FOG_COORDS(9)                    //this initializes the unity fog
};

VertexOutput vert (VertexInput v) {
     VertexOutput o = (VertexOutput)0;           
     o.uv0 = v.texcoord0;
     o.uv1 = v.texcoord1;
     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);
     o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
     o.posWorld = mul(_Object2World, v.vertex);
     UNITY_TRANSFER_FOG(o,o.pos);
     TRANSFER_VERTEX_TO_FRAGMENT(o)
     return o;
}

Fragment Program

fragment 程序里,在算法上要定义之后可以使用的变量Set:

float4 frag(VertexOutput i) : COLOR {

//normal direction calculations
     float3 normalDirection = normalize(i.normalDir);

     float3 lightDirection = normalize(lerp(_WorldSpaceLightPos0.xyz, _WorldSpaceLightPos0.xyz
 - i.posWorld.xyz,_WorldSpaceLightPos0.w));

     float3 lightReflectDirection = reflect( -lightDirection, normalDirection );

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

     float3 viewReflectDirection = normalize(reflect( -viewDirection, normalDirection ));

     float3 halfDirection = normalize(viewDirection+lightDirection); 

     float NdotL = max(0.0, dot( normalDirection, lightDirection ));

     float NdotH =  max(0.0,dot( normalDirection, halfDirection));

     float NdotV =  max(0.0,dot( normalDirection, viewDirection));

     float VdotH = max(0.0,dot( viewDirection, halfDirection));

     float LdotH =  max(0.0,dot(lightDirection, halfDirection));
 
     float LdotV = max(0.0,dot(lightDirection, viewDirection)); 

     float RdotV = max(0.0, dot( lightReflectDirection, viewDirection ));

     float attenuation = LIGHT_ATTENUATION(i);

     float3 attenColor = attenuation * _LightColor0.rgb;

根据 Unity Shader 指南的说明,将要用 Unity 提供的数据编译的变量。
这个变量移动到 BRDF 内部,会用在整体 Shader 上。

Roughness

下面的方法上会重新处理粗糙度。 我做这个的理由是个人喜好。
发现下方重新处理的粗糙度在视觉上的结果更分明。

float roughness = 1- (_Glossiness * _Glossiness);   // 1 - smoothness*smoothness
roughness = roughness * roughness;

Metallic

PBR shader里使用 Metallic的时候要小心的很多。
可以知道任何算法都不说明那个算法。所以我们用其他形式完全包含了。
金属材料是电介质 (非金属,也就是金属 = 0) 或者是决定是不是金属 (금속 = 1) 材料的控制值。
也就是说,为了我们的金属度值用正确的方式影响到Shader,我们要用扩散的颜色连接这些,诱导出我们的反射颜色。
金属不会显示扩散反射(Diffuse),所以有完全反射的 Albedo,实际的反射颜色会反映物体的表面去变更。
参考以下 :

float3 diffuseColor = _Color.rgb * (1-_Metallic) ;
float3 specColor = lerp(_SpecularColor.rgb, _Color.rgb, _Metallic * 0.5);

我们Shader内部

以下是我们要构建的基本shader形式。
写上意见,构成插入代码的位置,并告知。

Shader "Physically-Based-Lighting" {
    Properties { 
    _Color ("Main Color", Color) = (1,1,1,1)                    //diffuse Color
    _SpecularColor ("Specular Color", Color) = (1,1,1,1)        //Specular Color (Not Used)
    _Glossiness("Smoothness",Range(0,1)) = 1                    //My Smoothness
    _Metallic("Metalness",Range(0,1)) = 0                    //My Metal Value      


// future shader properties will go here!! Will be referred to as Shader Property Section
    }

    SubShader {
	Tags {
            "RenderType"="Opaque"  "Queue"="Geometry"
        } 
        Pass {
            Name "FORWARD"
            Tags {
                "LightMode"="ForwardBase"
            }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #define UNITY_PASS_FORWARDBASE
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #include "Lighting.cginc"
            #pragma multi_compile_fwdbase_fullshadows  
            #pragma target 3.0

float4 _Color;
float4 _SpecularColor;
float _Glossiness;
float _Metallic;

//future public variables will go here! Public Variables Section

struct VertexInput {
    float4 vertex : POSITION;       //local vertex position
    float3 normal : NORMAL;         //normal direction
    float4 tangent : TANGENT;       //tangent direction    
    float2 texcoord0 : TEXCOORD0;   //uv coordinates
    float2 texcoord1 : TEXCOORD1;   //lightmap uv coordinates
};

struct VertexOutput {
    float4 pos : SV_POSITION;              //screen clip space position and depth
    float2 uv0 : TEXCOORD0;                //uv coordinates
    float2 uv1 : TEXCOORD1;                //lightmap uv coordinates

//below we create our own variables with the texcoord semantic. 
    float3 normalDir : TEXCOORD3;          //normal direction   
    float3 posWorld : TEXCOORD4;          //normal direction   
    float3 tangentDir : TEXCOORD5;
    float3 bitangentDir : TEXCOORD6;
    LIGHTING_COORDS(7,8)                   //this initializes the unity lighting and shadow
    UNITY_FOG_COORDS(9)                    //this initializes the unity fog
};

VertexOutput vert (VertexInput v) {
     VertexOutput o = (VertexOutput)0;           
     o.uv0 = v.texcoord0;
     o.uv1 = v.texcoord1;
     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);
     o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
     o.posWorld = mul(_Object2World, v.vertex);
     UNITY_TRANSFER_FOG(o,o.pos);
     TRANSFER_VERTEX_TO_FRAGMENT(o)
     return o;
}    

//helper functions will go here!!! Helper Function Section


//algorithms we build will be placed here!!! Algorithm Section



float4 frag(VertexOutput i) : COLOR {

//normal direction calculations
     float3 normalDirection = normalize(i.normalDir);

     float3 lightDirection = normalize(lerp(_WorldSpaceLightPos0.xyz, _WorldSpaceLightPos0.xyz - i.posWorld.xyz,_WorldSpaceLightPos0.w));

     float3 lightReflectDirection = reflect( -lightDirection, normalDirection );

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

     float3 viewReflectDirection = normalize(reflect( -viewDirection, normalDirection ));

     float3 halfDirection = normalize(viewDirection+lightDirection); 

     float NdotL = max(0.0, dot( normalDirection, lightDirection ));

     float NdotH =  max(0.0,dot( normalDirection, halfDirection));

     float NdotV =  max(0.0,dot( normalDirection, viewDirection));

     float VdotH = max(0.0,dot( viewDirection, halfDirection));

     float LdotH =  max(0.0,dot(lightDirection, halfDirection));
 
     float LdotV = max(0.0,dot(lightDirection, viewDirection)); 

     float RdotV = max(0.0, dot( lightReflectDirection, viewDirection ));

     float attenuation = LIGHT_ATTENUATION(i);

     float3 attenColor = attenuation * _LightColor0.rgb;

     float roughness = 1- (_Glossiness * _Glossiness);   // 1 - smoothness*smoothness
     
     roughness = roughness * roughness;

     float3 diffuseColor = _Color.rgb * (1-_Metallic) ;
    
     float3 specColor = lerp(_SpecularColor.rgb, _Color.rgb, _Metallic * 0.5);
      
    //future code will go here!    Fragment Section

     return float4(1,1,1,1);
}
ENDCG
}
}
FallBack "Legacy Shaders/Diffuse"
}

添加到Unity里在材质时要使用Shader代码制作白色个体。
通过属性Shun, 变量section的变量, Helper 函数section的 Helper 函数,算法section的算法和 Fragments section的Shader代码实现扩张shader。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值