Forward路径中的StandardShader


这篇说一下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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容

  • 转载自VR设计云课堂[https://www.jianshu.com/u/c7ffdc4b379e]Unity S...
    水月凡阅读 970评论 0 0
  • <转>我也忘了转自哪里,抱歉,感谢原作者 什么是Shader Shader(着色器)是一段能够针对3D对象进行操作...
    星易乾川阅读 5,505评论 1 16
  • 李克富||“具象思维”是个什么鬼 思维:思维是人脑对客观事物本质和事物之间内在联系的认识,思维作为一种反应形式,它...
    呵呵无言阅读 280评论 0 0
  • 儿子, 这两天幼儿园面试,你没有如我们所期望的自信回答,当老师问你姓名和名字时你竟然都不回应,妈妈虽然当时表现的很...
    Syneysun阅读 127评论 0 0
  • 一 锦鲤吻荷 红腮粉面碧罗裙, 袅袅婷婷香醉人。 曲腿弓腰花下蹲。 到时辰……。 一半儿娇羞一半儿允。 二 寒霜 ...
    梅姿阅读 913评论 22 40