Forward路径中的StandardShader

2019-03-10  本文已影响0人  万里_aa3f

这篇说一下unity中的前向渲染路径。
我们经常用的材质编辑器不管是unity的standard shader还是UE4中的连线的编辑器,都是经过完整的封装的。以使它能够适配引擎中的渲染方式及很多参数的调整。当我们将标准材质编译出来,可以看到一个篇幅巨长的shader框架。这篇手动写一下适配于前向渲染的不透明物体的shader。
扫盲首先提出个问题,渲染路径的概念是描述什么的?有什么作用?
首先初学shader的人肯定知道兰伯特模型,还有之前写的unity中的PBS这些光照模型解决的问题是一栈灯光照到一个模型上,产生怎样的光照效果。而现实的场景中灯光不止一个,模型更是很多。如何合理的绘制出物体所受的每个光照的影响,就变成了一个麻烦的问题。渲染路径就是解决这个问题的概念。


shader支持如下效果

1.使用unity内置的BRDF算法;
2.支持前向渲染三种灯光及其衰减;
3.灯光阴影
4.支持除去最大的逐顶点计算的4个顶点光照(点光源)
5.间接光照、环境反射

总的来讲光照分为直接光照和间接光照。而在计算直接光照是的逐像素计算很费,这时候,前向渲染也需使用逐顶点和SH球谐方式光照的计算。其中逐像素计算是需要走一边BRDF公式的,也就是每个Pass(不管是Base还是Add)。
间接光照由于每个物体只需要计算一次,就放在BasePass中。

一.BasePass与AddPass中的逐像素光照

BRDF公式(在之前的文章中写到过):



上面是一个UNITY_PBS公式的输入、其中的albedo、specular(从metallic中推)、smoothness、normal、viewDir等可以从模型及贴图中获取到,两个Pass中这些信息是相同的。BasePass与AddPass真正区分传入的参数区分是光照参数。而光照参数分为两种、一种是DirLight、一种是InDirLight。InDirLight一会儿再谈。
unity中,每个直接光照的结算就会走一遍单独的pass,而BasePass与AddPass的区别在于,BassPass会计算一个平行光照的。AddPass会用计算多余的像素光照,之后用Blend One One的模式叠加上去。

1、用宏定义区分光照类型 #pragma multi_compile_fwdadd

不同的光照中,对light的dir和attenuation的计算方法是不同的
fwdadd替换结果

#pragma multi_compile_fwdadd
#pragma multi_compile DIRECTIONAL DIRECTIONAL_COOKIE POINT SPOT POINT_COOKIE

结合autoLight.cginc中的定义 ,判断了光源的类型

// If none of the keywords are defined, assume directional?
#if !defined(POINT) && !defined(SPOT) && !defined(DIRECTIONAL) && !defined(POINT_COOKIE) && !defined(DIRECTIONAL_COOKIE)
    #define DIRECTIONAL
#endif
2.不同类型光源的光照方向及光照衰减

UnityWorldSpaceLightDir(unityCG.cginc)

// Computes world space light direction, from world space position
inline float3 UnityWorldSpaceLightDir( in float3 worldPos )
{
    #ifndef USING_LIGHT_MULTI_COMPILE
        return _WorldSpaceLightPos0.xyz - worldPos * _WorldSpaceLightPos0.w;
    #else
        #ifndef USING_DIRECTIONAL_LIGHT
        return _WorldSpaceLightPos0.xyz - worldPos;
        #else
        return _WorldSpaceLightPos0.xyz;
        #endif
    #endif
}

举个点光源衰减的例子(AutoLight.cginc)

#ifdef POINT
sampler2D_float _LightTexture0;
unityShadowCoord4x4 unity_WorldToLight;
#   define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) \
        unityShadowCoord3 lightCoord = mul(unity_WorldToLight, unityShadowCoord4(worldPos, 1)).xyz; \
        fixed shadow = UNITY_SHADOW_ATTENUATION(input, worldPos); \
        fixed destName = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).r * shadow;
#endif

二、逐顶点光照

当光照计算超过设置的逐顶点计算的数量时,会将这是的光照信息甩入逐顶点计算的灯光中,顶点光照支持4个,再多就会甩入到SH光照。
Unity会使用VERTEXLIGHT_ON关键字查找基础渲染通道的着色器变量。
在BasePass中填入 #pragma multi_compile _ VERTEXLIGHT_ON
在UnityShaderVariables中定义 unity_4LightPosX0/unity_4LightPosY0/unity_4LightPosZ0等4个float4的数值 储存位置信息
顶点光源颜色数组 unity_LightColor[0].xyz
光源衰减 unity_4LightAtten0

内置函数
        o.vertexLightColor=Shade4PointLights(
            unity_4LightPosX0,unity_4LightPosY0,unity_4LightPosZ0,
            unity_LightColor[0].xyz,unity_LightColor[1].xyz,
            unity_LightColor[2].xyz,unity_LightColor[3].xyz,
            unity_4LightAtten0,o.worldPos,o.normal);
函数定义
// Used in ForwardBase pass: Calculates diffuse lighting from 4 point lights, with data packed in a special way.
float3 Shade4PointLights (
    float4 lightPosX, float4 lightPosY, float4 lightPosZ,
    float3 lightColor0, float3 lightColor1, float3 lightColor2, float3 lightColor3,
    float4 lightAttenSq,
    float3 pos, float3 normal)
{
    // to light vectors
    float4 toLightX = lightPosX - pos.x;
    float4 toLightY = lightPosY - pos.y;
    float4 toLightZ = lightPosZ - pos.z;
    // squared lengths
    float4 lengthSq = 0;
    lengthSq += toLightX * toLightX;
    lengthSq += toLightY * toLightY;
    lengthSq += toLightZ * toLightZ;
    // don't produce NaNs if some vertex position overlaps with the light
    lengthSq = max(lengthSq, 0.000001);

    // NdotL
    float4 ndotl = 0;
    ndotl += toLightX * normal.x;
    ndotl += toLightY * normal.y;
    ndotl += toLightZ * normal.z;
    // correct NdotL
    float4 corr = rsqrt(lengthSq);
    ndotl = max (float4(0,0,0,0), ndotl * corr);
    // attenuation
    float4 atten = 1.0 / (1.0 + lengthSq * lightAttenSq);
    float4 diff = ndotl * atten;
    // final color
    float3 col = 0;
    col += lightColor0 * diff.x;
    col += lightColor1 * diff.y;
    col += lightColor2 * diff.z;
    col += lightColor3 * diff.w;
    return col;
}

三、间接光照

间接光分为diffuse和specular
diffuse用来接收环境光照,用到球谐光公式,对此光照影响的因素有逐顶点光照、逐像素光照之外的灯光影响。和Lighting设置中,environment lighting参数的光照影响。
球谐光的实现原理:通过上述两方面的的光照,unity传入给shader7个float4,这定义在UnityShaderVariables中,之后通过 ShadeSH9(float4(i.normal.1)); 计算出来。

indirLight.diffuse+=max(0,ShadeSH9(float4(i.normal,1)));

specular间接高光、也就是我们常常见到的反射。这里的反射内容通过从天空求和反射捕捉中读取

        Unity_GlossyEnvironmentData envData;
        envData.roughness = 1 - GetSmoothness(i);
        float3 reflectDir=reflect(-viewDir,i.normal);

        float3 reflectDir1=BoxProjectedCubemapDirection(reflectDir, i.worldPos,unity_SpecCube0_ProbePosition,unity_SpecCube0_BoxMin, unity_SpecCube0_BoxMax);
        envData.reflUVW = reflectDir1;
        float3 probe0 = Unity_GlossyEnvironment(UNITY_PASS_TEXCUBE(unity_SpecCube0), unity_SpecCube0_HDR, envData);

        float3 reflectDir2=BoxProjectedCubemapDirection(reflectDir, i.worldPos,unity_SpecCube1_ProbePosition,unity_SpecCube1_BoxMin, unity_SpecCube1_BoxMax);
        envData.reflUVW = reflectDir2;
        float3 probe1= Unity_GlossyEnvironment(UNITY_PASS_TEXCUBE_SAMPLER(unity_SpecCube1, unity_SpecCube0),unity_SpecCube0_HDR, envData);
        indirLight.specular = lerp(probe1, probe0, unity_SpecCube0_BoxMin.w);

间接光照要考虑到AO,

        float AO = GetAO(i);
        indirLight.diffuse*=AO;
        indirLight.specular*=AO;

五、其他的一些

1.阴影

unity中的屏幕空间阴影流程:
1.计算场景深度,也是前向渲染的第一步。目的、从深度缓冲得到世界坐标。
2.阴影投射,灯光角度计算场景深度。所见到的深度,就是灯光照亮的区域。否则就是阴影。
3.collectShadow收集屏幕隐形,从场景的世界坐标向光源投射,查看在该光源的深度是否大于,阴影投射的深度。如果大于就说明该区域处在阴影之中。得到shadowMap
casterShadow,简单了直接用FallBack“Diffuse"
关于接收阴影的宏

TRANSFER_SHADOW(o);
SHADOW_COORDS(1)
SHADOW_ATTENUATION(i);
UNITY_LIGHT_ATTENUATION(attenuation,i,i.worldPos);
//base pass
#pragma multi_compile _ SHADOWS_SCREEN
//add pass
#pragma multi_compile_fwdadd_fullshadows

2.自发光

不用任何计算,直接加载最后的输出上

    fixed3 emissionCol=GetEmission(i);
    fixed3 finalCol=BRDF+emissionCol;

shader

尽量将shader部分写的清晰,可能导致不够精简,但没有关系unity编译会将重复代码合并;
就像surfaceShader,可以直接将我们的贴图参数传入GetAlbedo()/GetMetallic()中;

Shader"myshader/forwardGoOver"{
    Properties{
        _mainTex("MainTex",2D)="white"{}
        _Tine("Time",Color)=(1,1,1,1)
        _NormalScale("NormalScale",float)=1
        [NoScaleOffset]_MaskMap("MaskMap",2D)="white"{}
        [NoScaleOffset]_MetallicMap("MetallicMap",2D)="black"{}
        _Metallic("Metallic",Range(0,1))=0
        _Smoothness("smoothness",Range(0,1))=0
        _AoScale("AoScale",Range(0,1))=1
        [NoScaleOffset]_EmissionMap("EmissionMap",2D)="black"

    }

    SubShader{
        Tags{"RenderType"="Opaque"}
        pass{
            Tags{"LightMode"="ForwardBase"}

            CGPROGRAM
            #pragma target 3.0
            #pragma vertex vert 
            #pragma fragment frag 
            #pragma multi_compile _ VERTEXLIGHT_ON
            #pragma multi_compile _ SHADOWS_SCREEN
            #define FORWARD_BASE_PASS
            #include"ForwardLighting.cginc"
            ENDCG
        }
        pass{
            Tags{"LightMode"="ForwardAdd"}
            Blend one one 
            
            CGPROGRAM
            #pragma target 3.0
            #pragma vertex vert 
            #pragma fragment frag 
            #pragma multi_compile_fwdadd_fullshadows
      
            #include"ForwardLighting.cginc"

            ENDCG
        }
    }
    Fallback "Diffuse"
}
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

#if !defined(MY_LIGHTING_INCLUDED)
#define MY_LIGHTING_INCLUDED

#include"UnityPBSLighting.cginc"
#include"AutoLight.cginc"
#include"UnityStandardUtils.cginc"

sampler2D _mainTex;
float4 _mainTex_ST;
float3 _Tine;

sampler2D _NormalMap;
float _NormalScale;


sampler2D _MetallicMap;
float _Metallic;
float _Smoothness;



float _AoScale;
sampler2D _EmissionMap;

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

struct v2f{
    float4 pos:SV_POSITION;
    float3 worldPos:TEXCOORD0;
    float3 normal:TEXCOORD1;
    float4 tangent:TEXCOORD2;
    float2 uv:TEXCOORD3;
    float2 uv2:TEXCOORD6;
    #if defined(VERTEXLIGHT_ON)
        float3 vertexLightColor:TEXCOORD4;
    #endif
    SHADOW_COORDS(5)
};

void ComputevertexLight(inout v2f o){
    #if defined(VERTEXLIGHT_ON)
        o.vertexLightColor=Shade4PointLights(
            unity_4LightPosX0,unity_4LightPosY0,unity_4LightPosZ0,
            unity_LightColor[0].xyz,unity_LightColor[1].xyz,
            unity_LightColor[2].xyz,unity_LightColor[3].xyz,
            unity_4LightAtten0,o.worldPos,o.normal);
    #endif
}

v2f vert(a2v v){
    v2f o;
    o.pos=UnityObjectToClipPos(v.vertex);
    o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
    o.normal=UnityObjectToWorldNormal(v.normal);
    o.tangent=float4(UnityObjectToWorldDir(v.tangent.xyz),v.tangent.w);
    o.uv=TRANSFORM_TEX(v.uv,_mainTex);
    o.uv2=v.uv1;
    TRANSFER_SHADOW(o);
    ComputevertexLight(o);
    return o;
}

float3 GetAlbedo(v2f i){
    return tex2D(_mainTex,i.uv).xyz * _Tine.xyz;
}

float GetMetallic(v2f i){
    return tex2D(_MetallicMap,i.uv).r + _Metallic;
}

float GetSmoothness(v2f i){
    return _Smoothness;
}

float3 GetNormal(v2f i){
    float3 worldNormal=normalize(i.normal);
    float3 worldTangent=normalize(i.tangent.xyz);
    float3 worldBinormal=cross(worldNormal,worldTangent) * i.tangent.w * unity_WorldTransformParams.w;

    float3 tangentNormal=UnpackScaleNormal(tex2D(_NormalMap,i.uv),_NormalScale);
    
    return float3(
        tangentNormal.x*worldTangent+
        tangentNormal.y*worldBinormal+
        tangentNormal.z*worldNormal
    );
}

float GetAO(v2f i){
    return 1.;
}

UnityLight GetdirLight(v2f i){
    UnityLight dirLight;
    dirLight.dir=UnityWorldSpaceLightDir(i.worldPos);

    UNITY_LIGHT_ATTENUATION(attenuation,i,i.worldPos);
    dirLight.color=_LightColor0.xyz * attenuation;
    return dirLight;
}

UnityIndirect GetindirLight(v2f i,float3 viewDir){
    UnityIndirect indirLight;
    indirLight.diffuse=0;
    indirLight.specular=0;
    #if defined(VERTEXLIGHT_ON)
        indirLight.diffuse+=i.vertexLightColor;
    #endif

    #if defined(FORWARD_BASE_PASS)
        indirLight.diffuse+=max(0,ShadeSH9(float4(i.normal,1)));

        Unity_GlossyEnvironmentData envData;
        envData.roughness = 1 - GetSmoothness(i);
        float3 reflectDir=reflect(-viewDir,i.normal);

        float3 reflectDir1=BoxProjectedCubemapDirection(reflectDir, i.worldPos,unity_SpecCube0_ProbePosition,unity_SpecCube0_BoxMin, unity_SpecCube0_BoxMax);
        envData.reflUVW = reflectDir1;
        float3 probe0 = Unity_GlossyEnvironment(UNITY_PASS_TEXCUBE(unity_SpecCube0), unity_SpecCube0_HDR, envData);

        float3 reflectDir2=BoxProjectedCubemapDirection(reflectDir, i.worldPos,unity_SpecCube1_ProbePosition,unity_SpecCube1_BoxMin, unity_SpecCube1_BoxMax);
        envData.reflUVW = reflectDir2;
        float3 probe1= Unity_GlossyEnvironment(UNITY_PASS_TEXCUBE_SAMPLER(unity_SpecCube1, unity_SpecCube0),unity_SpecCube0_HDR, envData);
        indirLight.specular = lerp(probe1, probe0, unity_SpecCube0_BoxMin.w);

        float AO = GetAO(i);
        indirLight.diffuse*=AO;
        indirLight.specular*=AO;
    #endif

    
    return indirLight;
}

float3 GetEmission(v2f i){
    float3 emission=float3(0,0,0);
    #if defined(FORWARD_BASE_PASS)
        emission=tex2D(_EmissionMap,i.uv).xyz;
    #endif
    return emission;
}


fixed4 frag(v2f i):SV_TARGET{
    float3 albedo=GetAlbedo(i);

    float metallic=GetMetallic(i);
    float3 specular;
    float OneMinuseReflectivity;
    albedo = DiffuseAndSpecularFromMetallic(albedo,metallic,specular,OneMinuseReflectivity);

    float smoothness=GetSmoothness(i);
    float3 normal=GetNormal(i);
    float3 viewDir=normalize(UnityWorldSpaceViewDir(i.worldPos));

    UnityLight dirLight=GetdirLight(i);
    UnityIndirect indirLight=GetindirLight(i,viewDir);

    fixed3 BRDF=UNITY_BRDF_PBS(albedo,specular,OneMinuseReflectivity,smoothness,normal,viewDir,dirLight,indirLight);
    fixed3 emissionCol=GetEmission(i);
    fixed3 finalCol=BRDF+emissionCol;
    return fixed4(finalCol,1.0);
}

#endif
上一篇下一篇

猜你喜欢

热点阅读