【Unity 手写PBR】补充:多光源 阴影 视差 自发光

写在前面

【Unity 手写PBR】Build-in管线:实现直接光部分

【Unity 手写PBR】Build-in管线:实现间接光部分

这里再快速补充一下剩下的部分,多光源、阴影、视差贴图应用和自发光。

1 多光源

环境光照和自发光都在Base Pass计算,Additonal Pass只计算直接光照,其中atten是光照衰减值:

float3 result = directLight * atten;

由于是在Unity固定管线下,所以还是采用老办法AdditionalPass,关于Unity前向渲染如何处理多光源的可以参考之前我写的一篇文章【技术美术图形部分】关于前向渲染和延迟渲染

实现的话,如果不去深究,直接拿Unity的内置宏,一句话解决:

// 光源衰减项
UNITY_LIGHT_ATTENUATION(atten,i, worldPos);

如果要自己实现的话,尤其注意光源衰减值,平行光、点光源、聚光灯都是有区别的,贴个自己写的涉及到衰减项的部分shader:

        // 光源衰减项
        #ifdef USING_DIRECTIONAL_LIGHT
            float atten = 1.0;
        #else
        #if defined(POINT)
            // 点光源从世界变到光源空间
            float3 lightCoord = mul(unity_WorldToLight, float4(worldPos,1)).xyz;
            float atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
        #elif defined(SPOT)
            float4 lightCoord = mul(unity_WorldToLight, float4(worldPos,1));
            float atten = (lightCoord.z>0)*tex2D(_LightTexture0, lightCoord.xy/lightCoord.w+0.5).w*tex2D(_LightTextureB0,dot(lightCoord,lightCoord).rr).UNITY_ATTEN_CHANNEL;
        #else
            float atten = 1.0;
        #endif
        #endif

2 阴影

阴影这里全调用Unity内置宏解决,需要保证物体能投射阴影,还要保证物体能接受阴影。需要注意的是,

BasePass中,对主要平行光的计算,要么

UNITY_LIGHT_ATTENUATION(atten,i, worldPos);

一下解决atten计算和采样纹理shadow值的问题;要么

        float shadow = 1;
        float atten = 1.0; // 仅平行光
        
        shadow = SHADOW_ATTENUATION(i); // 使用_ShadowCoord对相关纹理采样,得到阴影信息

规定平行光的atten为1后,还要计算shadow值,最后result直接光部分也加上*atten*shadow,保证可以接受阴影。 

其余情况,擅用宏就行,由于比较分散,这里就不一个一个贴出来了,,,之前写过一篇【Unity Shader】Unity中阴影映射标准制作流程,里面有写实现方法,需要的话可以去看看~

2 视差贴图

参考:2.5凹凸映射、视差映射、浮雕映射 - 知乎 (zhihu.com)

视差算法:关于视差算法的基本原理与拓展应用 - 哔哩哔哩 (bilibili.com) 

Unity Standard Shader里还有Height Map的应用,为了让我的PBR Shader更加完整,加上!

2.1 简单解释

之前一直都分不清法线贴图、视差贴图这些,,后来发现视差贴图经常是与法线贴图一起使用。法线贴图改变了纹理的光照,视差贴图再叠加叠加,视差会移动纹理的可见项,实现表面的遮挡效果,感觉就是“更加真实”了。

随便搜了搜,拿这篇文章里的图举例:

中间是BaseColor+NormalMap,右边的是BaseColor+NormalMap+ParallaxMap

所以视差贴图解决的更多的是结构之间的遮挡问题。 

2.2 实现

之所以是“视差”,其核心是改变纹理坐标,利用模型表面的高度信息对纹理进行偏移,低位置信息被高位置遮挡了,所以直接采样更高的信息。

fragment shader中要采样视差贴图得到Height值,再通过ParallaxOffset得到偏移量Offset,应用到i.uv上就行。

        // 视差贴图
        float height = tex2D(_ParallaxMap, i.uv).r;
        
        float2 offset = ParallaxOffset(height, _ParallaxScale, i.objviewDir);
        i.uv += offset;

这里的ParallaxOffset是UnityCG.cginc定义的函数,源码:

// Calculates UV offset for parallax bump mapping
inline float2 ParallaxOffset( half h, half height, half3 viewDir )
{
    h = h * height - height/2.0;
    float3 v = normalize(viewDir);
    v.z += 0.42;
    return h * (v.xy / v.z);
}

h为传入的采样值,height为我们定义的_ParallaxScale,用viewDir的x和y分量,反正就直接用这个源码就OK!

2.3 对比

加了一个点光源,对比明显一些:

仅基础色

然后是NormalMap拉到最大:

基础色+法线

这个是只加上ParallaxMap,效果拉满:

基础色+视差

我不太清楚这样对比是否合适,但能感觉到视差到底做了什么事,就是一个假的、凹凸的效果。

关于视差就到这。

3 自发光贴图

自发光虽然简单,但我的PBR shader必须什么功能都有!搞一下:

自发光感觉是不受到光照影响的,所以说传入的自发光贴图就是个黑白的通道而已,就针对区域上色,最后result+=就行了:

float3 emission = tex2D(_EmissionTex, i.uv);
emission = _EmissionColor * emission * _EmissionStrength;

后期bloom的话还需调整参数。


写的很仓促,目的仅仅是为了记录,后面会搭个场景,尽量把不同的材质都体现在场景中,也算是给这4、5天的PBR学习做一个收尾工作吧。

猜你喜欢

转载自blog.csdn.net/qq_41835314/article/details/129755845