如何在UE4中制作无光照次表面玉石材质球

如何在UE4中制作无光照次表面玉石材质球

最近有很多小伙伴都收到了虚幻官方的材质球,我运气不是很好没有抽到,所以只能自己在引擎里面做一个了,当然不能满足于普通的材质,这次我们直接上玉石的SSS材质,下面是大图。

好了下面我们就正式开始制作,这次制作不适用UE的subsurface光照模型,我们直接用无光照的模式实现。
次表面的实现主要是根据吸收率(absorb)和散射率(scatter)计算出物体颜色的变化。如果是非常精确的做法,需要利用模拟次表面的几种物理公式来计算,比较复杂,我这边就做个简单的模拟,利用普通的一个指数函数来简单的模拟吸收率和散射率。由于是基于物体表面的所以我们需要一个法线来计算散射率和吸收率。
法线:计算物体表面,我们用worldposition减去objectposition后normalize即可得到法线,然后将worldposition的位置变为local即可(让效果能跟着物体移动而不是worldposition)
由于法线的计算中其实有负数部分,我们可以通过计算来避免负数出现,这边的方法如下
nrm=normalize(nrm*scat_range+scat_offset);

法线

散射率和吸收率:散射率和吸收率我们用2^(-2*nrml)乘以一个数值表示

2^{-2x}d1的形状

我们定义scatter(散射率)mu_s为散射率的倍数(需要控制在0-1)
float mu_s=0.2;
float scatter = clamp(exp2(-2 * nrm )*mus, 0, 1)

我们继续定义absorb(吸收率)mu_a为吸收的倍数(需要控制在0-1)
float mu_a=0.8;
float absorb =clamp(exp2(-2 * nrm )*mu_a,0,1);

倍数大家可以自行定义,我这边差不多给了两个值。

scatter结果


菲涅尔:万物皆有菲涅尔,但是我们这边并不是随着视线变化,我们这里得随着normal变化。我们首先得定义它的金属度metal,可以将metal设置为0.2,1的话就是金属了,推荐设置0.2-0.3之间。
然后就可以用对我们上面获得的法线进行power来简单的模拟一下
float fres = metal + (1 - metal) * clamp(pow(1 - nrm, 5.0), 0, 1);
加上metal之后就可以通过控制metal控制金属度。

菲涅尔的结果

表面反射:有了菲涅尔之后我们就可以计算它的颜色了,但是由于是玉石,表面一般都很光滑,我们这边是无光照模式,所以得采样一张cubemap贴在反射上来模拟表面的粗糙度。
float3 cube = TextureCubeSample(Cube, CubeSampler, uv);
float3 refl = reflect_int*cube;
我们用reflect_int乘以原来的cubemap来控制表面的反射强度(反射值越高粗糙度越低,塑料感越强,需要控制在合适的范围内)我这边设置的值为0.5。

refl效果

颜色:最后就是颜色了,颜色的话分为三个部分,一个是反射,一个是散射掉的,一个是吸收掉的。我们只需要将这三个部分加起来就可以了。
直接用菲涅尔的结果乘以反射值可以简单的模拟出表面的反射随光照的影响,
反射:fres*refl
然后在菲涅尔的反向部分是吸收和散射
散射:(1 - fres)*absorb*scatter
吸收: (1-fres)*absorb*(1.0-scatter)
最后加在一起
float3 color = fres*refl+ (1 - fres)*absorb*scatter+(1-fres)*absorb*(1.0-scatter);

color

然后还可以用color乘以一个颜色来控制他的颜色倾向
花纹:里面的花纹的话可以在吸收部分乘以一张贴图进行,然后可以根据贴图的黑白来lerp不同的颜色
float4 marbleTex=Texture2DSample(Tex,TexSampler,uv2);
颜色最后的表达式:
color = fres*refl+ (1 - fres)*absorb*scatter+(1-fres)*absorb*(1.0-scatter)*color_offset*lerp(float3(0.8,0.8,0.8),float3(1,0.25,0),length(marbleTex.xyz)*0.5);

大理石花纹
最终颜色

然后我们需要和灯光关联起来,我这边也不是做的任意位置的灯光关联,强度也没有计算,只是将灯光的旋转值传到了材质里,然后对法线进行旋转。(任意角度的计算太复杂了,放弃了)
首先我们先在代码中写好三个旋转矩阵并定义成function(不懂的请补习数学)

struct rotation
{
    float3x3 rotateZ(float theta)
    {
        float c = cos(theta);
        float s = sin(theta);
        return float3x3(
        float3(c, -s, 0),
        float3(s, c, 0),
        float3(0, 0, 1));
    }
  float3x3 rotateY(float theta)
    {
        float c = cos(theta);
        float s = sin(theta);
        return float3x3(
        float3(c, 0, s),
        float3(0, 1, 0),
        float3(-s, 0, c));
   }
  float3x3 rotateX(float theta)
    {
        float c = cos(theta);
        float s = sin(theta);
        return float3x3(
        float3(1, 0, 0),
        float3(0, c, -s),
        float3(0, s,  c));
    }
};
rotation rota;

然后将法线分别乘以三个变换矩阵就可以了(theta值为弧度)

nrm=normalize(mul(rota.rotateZ(thetaz),nrm));
nrm=normalize(mul(rota.rotateY(thetay),nrm));
nrm=normalize(mul(rota.rotateX(thetax),nrm));

最后我们新建一个材质参数集,新建一个三维的向量rota,然后再新建一个actor放入灯光,将灯光actor的旋转值传给参数集,并设置为自传。

light actor
传给参数集(都需要转化为弧度)

最后在材质编辑器里调用参数集将值传输给thetax,thetay,thetaz

传输参数

最后就能获得如下的效果了(颜色的话和参数集一样外部控制就行了)

最终效果
custom节点
所有节点
材质实例的参数设置

文章中有不对或者不足之处请大佬们斧正,敬谢!最后附上所有代码。

struct rotation
{
    float3x3 rotateZ(float theta)
    {
        float c = cos(theta);
        float s = sin(theta);
        return float3x3(
        float3(c, -s, 0),
        float3(s, c, 0),
        float3(0, 0, 1));
    }
  float3x3 rotateY(float theta)
    {
        float c = cos(theta);
        float s = sin(theta);
        return float3x3(
        float3(c, 0, s),
        float3(0, 1, 0),
        float3(-s, 0, c));
   }
  float3x3 rotateX(float theta)
    {
        float c = cos(theta);
        float s = sin(theta);
        return float3x3(
        float3(1, 0, 0),
        float3(0, c, -s),
        float3(0, s,  c));
    }
};
rotation rota;;
float3 color = 0;
float mu_a=0.8;
float mu_s=1;
float4 marbleTex=Texture2DSample(Tex,TexSampler,uv2);
float3 nrm =normal;
nrm=normalize(mul(rota.rotateZ(thetaz),nrm));
nrm=normalize(mul(rota.rotateY(thetay),nrm));
nrm=normalize(mul(rota.rotateX(thetax),nrm));
nrm=normalize(nrm*scat_range+scat_offset);
float fres = metal + (1 - metal) * clamp(pow(1 - nrm, 5.0), 0, 1);
float3 cube = TextureCubeSample(Cube, CubeSampler, uv);
float3 refl = reflect_int*cube;
float scatter = clamp(exp2(-2 * nrm )*mu_s, 0, 1);
float  absorb =clamp(exp2(-2 * nrm )*mu_a,0,1);
color = fres*refl+ (1 - fres)*absorb*scatter+(1-fres)*absorb*(1.0-scatter)*color_offset*lerp(float3(0.8,0.8,0.8),float3(1,0.25,0),length(marbleTex.xyz)*0.5);
return color ;

编辑于 2021-01-20 18:56