PBR渲染,阿库娅的计算机图形学实践(阶段性总结)

从4.12上手,进行一个阶段性总结,作为复习,也顺便瞻前顾后一下。

(当然,只有暂时记得住的东西)

1:TBN矩阵

TBN矩阵的本质就是沟通起法线贴图世界空间下的法线的通道。

TBN矩阵毕竟还是一个矩阵

具体构成有:

T(切向方向)

B(副切线方向)

N(世界空间法线方向)

传统意义上的法线贴图里存在了模型的法线方向的各种信息,我们可以在PS中从贴图中找到法线贴图

TBN矩阵的具体使用方向:

是个矩阵,就要获取来自模型中的顶点的各种属性,比如,切线,副切线,还有世界坐标下的法线。

现在构造顶点找色器的结构体中拿去来自Unity的API,构造好几个贴图通道,

分别是世界空间法线位置(normalws),世界顶点位置(positionWS),物体到摄像机的方向向量(viewDirWS),世界空间下顶点的切线向量(tangentWS),世界空间下顶点的副切线方向(bitangentWS)

然后就是顶点着色器的编写:

其中:输入来自结构体的各种变换。

从TBN矩阵中来看就是赋值,给结构体类型的OUTPUT的属性中输入来自模型中的世界空间法线向量:

给切线tangentWS从物体空间转换为世界空间。切线在纹理映射中非常重要,尤其是在法线贴图等效果中,用来影响光照计算。

副切线同理,但副切线是切线与法线的叉积,表示纹理的“垂直”方向。

至此,顶点找色器处理完毕,接下来是片元找色器方面。

获取法线贴图,进行归一化。

菲涅尔效果,Fresnel效果用于模拟表面反射强度随着视角变化的效果。

随后便是TBN矩阵构建。输入来自顶点的三个向量。

viewDir在经过TBN矩阵转化后,其中包含了法线贴图中的方向和位置信息。用viewTS作为名字储存

至此,viewTS便是通过TBN矩阵变换后的ViewDir,里面包含了法线贴图的许多信息,

就可以让法线贴图的作用在模型上得到彰显。

2:RIm边缘光和菲涅尔效应的区别

当初在了解菲涅尔效应后总感觉和边缘光好像,今天就进行一下理解和区分:

菲涅尔公式:

菲涅尔模拟的是,光线在远处的边缘反射,比如这一部分

越远,光线在河上反射的强度就越高,因此,这一效果通常被用来模拟金属边缘或者透明材质;

Rim边缘光:

Rim边缘光指的就是摄像机从正面看角色,看不到任何效果,但是在边缘方向,和摄像机视角越垂直,颜色表现就越明显。

更多的只是在进行一种光照操作,就用最简单的办法解决好模型表面的视觉效果。

但这并不符合现实

所以,往往菲涅尔效果会被运用到现实渲染,而边缘光往往用在非现实渲染上。

3:SDF卡通渲染

具体文章借鉴这位大佬的知乎专栏

模仿原神的卡通渲染,该功能主要是在应对传统的光照模型下对阴影的紊乱,所采取的改进。

简单来讲,就是生成一张纹理阴影贴图,给它贴在角色的表面上。模拟好在光影变化。

逻辑是:

光照-》打在脸上-》和NdotL接近的部分-》阴影相对来说较大,相反,在背光处阴影较小。

1:获取SDF贴图:

2:先模拟好前方和右方的向量:

3:计算好光线打造角色脸上的光线效应:

把光线拉到0到1的范围内,进行平方只是为了让变化更加缓和,这样就能模拟光从右边打过来的效果。

这一段看起来比较复杂,但其实就执行了一个操作,如果光从左边打过来,那就翻转UV,重新执行一次操作就行。

光照模型:

1:CookTorrance光照模型

这一篇我已经做过了,详细得可转到:

双向链接

在这先简单的说明一下

公式贴在这里了:

这个模型是在维持住现实光照下的能量守恒所模拟的光照模型。所以,实际上这个模型能模拟出更好的现实光照。

D:微平面法线分布函数。

简单来说,就是自己定义表面的粗糙度,所模拟出光线在这个粗糙度下的反射状况。

F:菲涅尔效应。上面已经写清楚了。

G:微平面几何遮挡效应。

描述了表面微结构的遮挡效应,考虑了光线在表面上的反射是否被其他微面阻挡。

三个代码就贴在这里了:

float Fresnelfunction(float fresnelPower,float NdotL,float fresnelIntensity){

    fresnelPower=saturate(fresnelPower);
    fresnelIntensity=saturate(fresnelIntensity);
    return max(0.3,fresnelIntensity+(1-fresnelPower)*pow(abs(1-NdotL),fresnelIntensity));
}
float D_GGX(float3 H,float3 N,float alpha){
    float NdotH=max(dot(N,H),0);
    float alpha2=alpha*alpha;
    float denom=1+(alpha2-1)*NdotH*NdotH;
    return alpha2/(PI*denom*denom);
}
float G_SchilickGGX(float3 V,float3 L,float3 N,float alpha){
    float k=(alpha+1)*(alpha+1)/8.0;
    float NdotL=max(dot(N,L),0);
    float NdotV=max(dot(N,V),0);

    NdotL=smoothstep(0.4,1.0,NdotL);
    NdotV=smoothstep(0.4,1.0,NdotV);
    float G=NdotL/(NdotL*(1.0-k)+k)*NdotV/(NdotV*(1.0-k)+k);
    return G;
}
float G_Smith(float3 V,float3 L,float3 N,
    float alpha,float fresnelPower,
    float fresnelIntensity,
    float AttenuationIntensity,
    float useAniso,float3 T,float3 B,float Anisotropy){
    float UseAniso = saturate(useAniso);
    float k=(alpha+1)*(alpha+1)/8.0;
    float LdotN=max(dot(L,N),0.0);
    float VdotN=max(dot(V,N),0.0);
    float3 H=normalize(L+V);
    float F=Fresnelfunction(fresnelPower,LdotN,fresnelIntensity);

    float D=lerp(D_GGX(H,N,alpha),D_anisotropy(alpha,Anisotropy,H,T,B,N),useAniso);
    float G=lerp(G_SchilickGGX(V,L,N,alpha),G_anisotropy(T,V,L,B,alpha,Anisotropy,N),useAniso);

    float R=F*D*G/(4*LdotN*VdotN);
    return max(R,AttenuationIntensity);
}

无各向异性下的具体效果:

无渲染

有光照模型下的结果

2:Kajiya-Kay光照模型

虽然这么说本末倒置,但我的路线确实这样学的。

1:来源可以看这篇文章

各向异性头发渲染

2:原理

各向异性(英语:Anisotropy),或作异向性、非均向性,与各向同性相反,指物体的全部或部分物理、化学等性质随方向的不同而有所变化的特性,例如石墨单晶的电导率在不同方向的差异可达数千倍,又如天文学上,宇宙微波背景辐射亦拥有些微的非均向性。

而Kajiya模型确实是比较合理且简单的各向异性模型了。

这是代码:

float StrandSpecular(float3 T, float3 V, float L, float exponent)
{
    float3 H = normalize(L + V);
    float dotTH = dot(T, H);
    float sinTH = sqrt(1.0 - dotTH*dotTH);
    float dirAtten = smoothstep(-1.0, 0.0, dot(T, H));

    return dirAtten * pow(sinTH, exponent);
}

其中,困惑的其实还是对T和B的判定。

T:物体表面的切线。

B:某一点的副切线。

以下是我的理解:

由于模型和UV的关系,所以,我们在贴UV贴图的时候,总是有两类坐标,模型坐标和纹理坐标,模型坐标定义了模型的起起伏伏,各种变化。

纹理坐标定义了模型的某个点位置该为什么颜色之类的。

重点还是理解dirAtten:

从这个图来看

图片来自这位知乎大佬

如果我们把头发看成一个圆柱,我们可以假定B,

B:副切线,意味着和头发丝同样方向。

T:切线,头发丝的切线方向。

具体图片

注意,这个切线在我的理解里在世界坐标并不存在,它和B映射的是纹理坐标,B是世界空间中和纹理空间中都存在的东西。

在世界空间里,B意味着头发的方向。

T和B构成纹理坐标。N代表世界法线。世界法线垂直于T和B构成的纹理坐标系

不妨从代码中来理解原理:

  for ( int i=0; i<vertices.size(); i+=3)
        {

        // Shortcuts for vertices
        glm::vec3 & v0 = vertices[i+0];
        glm::vec3 & v1 = vertices[i+1];
        glm::vec3 & v2 = vertices[i+2];

        // Shortcuts for UVs
        glm::vec2 & uv0 = uvs[i+0];
        glm::vec2 & uv1 = uvs[i+1];
        glm::vec2 & uv2 = uvs[i+2];

        // Edges of the triangle : postion delta
        glm::vec3 deltaPos1 = v1-v0;
        glm::vec3 deltaPos2 = v2-v0;

        // UV delta
        glm::vec2 deltaUV1 = uv1-uv0;
        glm::vec2 deltaUV2 = uv2-uv0;

        float r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
        glm::vec3 tangent = (deltaPos1 * deltaUV2.y   - deltaPos2 * deltaUV1.y)*r;
        }

代码来自

这是关于切线的解读,直接看最后一句话。先把和现实坐标关联的pos部分先不看,假如没有pos那部分,那么切线算的就是贴图空间内的切线。也就是他的导数。

理解好在没有涉及世界坐标下的pos后,我们来看看加入Pos后是个什么样子?

当引入位置坐标pos后,切线变得与模型表面的几何形状紧密相关,并且涉及到如何将纹理坐标变化映射到三维模型表面上

也就是说,uv坐标收到了世界空间的影响。

借此来算出世界空间下的切线。

有了切线和法线,就能进行向量叉乘,得到副切线,而副切线意思就是发丝的方向。


                output.tangentWS = float4(T, input.tangentOS.w);
                output.bitangentWS = cross(output.normalWS, output.tangentWS) * input.tangentOS.w;
 

所以,模型中副切线的变化才是切线变化的根本原因,只要对副切线进行一些改进,就操控副切线的变化。

具体如下:

这是加了法线贴图后的结果。

3:各向异性改进下的CookTorrance光照模型:

为什么要改进?
  • 现实世界中的许多表面并不是 各向同性的,例如 布料、丝绸、木材和金属 等表面具有明显的纹理和方向性,这种表面的光反射强度是方向依赖的。

  • 传统的 各向同性 Cook-Torrance 模型 在这种表面上会出现一些不真实的光照效果,例如在反射方向上无法产生真实的光泽感。

所以目的确认:为了更加真实。

具体文章参考:

Position-free Multiple-bounce Computations for Smith Microfacet BSDFs笔记 - Pangaolee的文章 - 知乎

实时渲染的门门道道:从基本光照模型到PBR和NPR - 鱼露的文章 - 知乎https://blog.csdn.net/linjf520/article/details/133755168?fromshare=blogdetail&sharetype=blogdetail&sharerId=133755168&sharerefer=PC&sharesource=Kaliaailia&sharefrom=from_link

那么代码如下。


inline float D_anisotropy(half _Anisotropy,float r,float3 H,float3 T,float3 B,float3 N){
    half anisotropy=_Anisotropy;
    float at=max(pow(r*(1.0+anisotropy),2),0.001);
    float ab=max(pow(r*(1.0-anisotropy),2),0.001);
    half a_2=at*ab;
    half TdotH=dot(T,H);
    half BdotH=dot(B,H);
    half NdotH=dot(N,H);
    float D_2=1/((PI * at * ab) * pow((pow(TdotH, 2) / pow(at, 2) + 
    pow(BdotH,2)/pow(ab,2) + 
    pow(NdotH,2)),2));
    float normalization=1.0/(at*ab+1e-4);
    return D_2*normalization;
}

inline float G_anisotropy(float3 t,float3 v,float3 l,float3 b,float r,float anisotropy,float N)
{
    float at=max(pow(r*(1.0+anisotropy),2),0.001);
    float ab=max(pow(r*(1.0-anisotropy),2),0.001);
    half TdotV = dot(t, v);
    half TdotL = dot(t, l);
    half BdotV = dot(b, v);
    half BdotL = dot(b, l);
    half NdotV = dot(N, v);
    half NdotL = dot(N, l);
    float lambdaV = NdotL * length(half3(at * TdotV, ab * BdotV, NdotV));
    float lambdaL = NdotV * length(half3(at * TdotL, ab * BdotL, NdotL));
    float v_a = saturate(0.5 / (lambdaV + lambdaL));
    return v_a;
}

改进还是对D,微表面平面的改进和对G,微表面遮挡的改进

共同部分:引入各向异性效果,超过0就表示为圆环似的高光,0就表示为点状高光、

atab 分别表示在切线方向(T)和副切线方向(B)的 微表面法线分布

T,B切线方向和副切线方向,具体以及在上文中有所解释,

效果如下

D:

多了这一项:

float D_2=1/((PI * at * ab) * pow((pow(TdotH, 2) / pow(at, 2) + 
    pow(BdotH,2)/pow(ab,2) + 
    pow(NdotH,2)),2));
    float normalization=1.0/(at*ab+1e-4);
    return D_2*normalization;

G:

inline float G_anisotropy(float3 t,float3 v,float3 l,float3 b,float r,float anisotropy,float N)
{
    float at=max(pow(r*(1.0+anisotropy),2),0.001);
    float ab=max(pow(r*(1.0-anisotropy),2),0.001);
    half TdotV = dot(t, v);
    half TdotL = dot(t, l);
    half BdotV = dot(b, v);
    half BdotL = dot(b, l);
    half NdotV = dot(N, v);
    half NdotL = dot(N, l);
    float lambdaV = NdotL * length(half3(at * TdotV, ab * BdotV, NdotV));
    float lambdaL = NdotV * length(half3(at * TdotL, ab * BdotL, NdotL));
    float v_a = saturate(0.5 / (lambdaV + lambdaL));
    return v_a;
}

问题:

1:在各向异性高光下的部分全黑(全白)

比如这里

对象,Kajiya-Kay模型。

改进:

float StrandSpecular=max(StrangSpecular(shiftedT,viewDir,lightDir,_StrangSpecularexponent,_Specularscale),0);

加个最大值限制就行,因为这是因为高光背面没有被考虑的问题,所以只要限定最小就行了,

同理,发白是最大没有被限制,最小设置为1就行。

对象:改进后的BDRP

R=min(R,1.0);

未完待续。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值