美文网首页
Forward路径中的StandardShader

Forward路径中的StandardShader

作者: 万里_aa3f | 来源:发表于2019-03-10 17:55 被阅读0次

    这篇说一下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
    

    相关文章

      网友评论

          本文标题:Forward路径中的StandardShader

          本文链接:https://www.haomeiwen.com/subject/dxqupqtx.html