专栏名称: 游戏开发技术教程
网易十年码农,教程、内推、解惑。游戏开发技术、技巧、教程和资源下载,答疑解惑,内推面试。Unity3D、UnrealEngine(UE4、UE5)引擎,C#、C++等语法,图形渲染、物理动画、原理机制、源码剖析等及面试笔试题、职业规划。
目录
相关文章推荐
南京零距离  ·  3·15线索征集 | 护航新消费 帮忙零距离~ ·  2 天前  
南京零距离  ·  3·15线索征集 | 护航新消费 帮忙零距离~ ·  2 天前  
51好读  ›  专栏  ›  游戏开发技术教程

【UnityURP】复刻原神三渲二效果学习笔记完整版

游戏开发技术教程  · 公众号  · 科技自媒体  · 2024-06-25 20:34

正文

不积跬步,无以至千里。——荀子《劝学》

前言

之前写的那篇文章,最终实现的效果不够完整,这次使用URP管线并把代码升级成了HLSL,并完善了之前没完善的一些内容。再重新写一篇文章,整理一下学习内容,也当作自己的学习笔记,方便以后回顾。

学习UnityShader以来,最大的收获是美术思维到数学思维的转变。比如黑色不再是黑色;而是0.0,白色不再是白色,而是1.0。这样才能理解代码上的加减乘除有什么意义。现在网上原神的渲染文章一抓一大把,抄一段代码很简单,但如果不能理解其含义,也只能算是徒有其表罢了。

本文最终实现效果:

接下来介绍复刻的过程,因为部分内容和之前的效果一样,有些我就直接从之前的那篇文章复制过来了。

———————————————————————————————————————

贴图设置

先看一下原神的角色一共有哪些贴图,这里用芙芙演示,本文用到的所有模型贴图等资源均来自GitHub。

原神的角色贴图,资源来自GitHub

贴图分别有身体的Diffuse、Lightmap、Shadow_Ramp、Normalmap,头发的Diffuse、Lightmap、Shadow_Ramp、Normalmap,脸部的Diffuse、FaceLightmap,还有金属的MetalMap,剩下的Shadow和Specular_Ramp没有用上,可以忽略。注意老角色是没有Normal的(应该是大约3.0之后的角色才有)。

然后贴图导入Unity后需要注意一下设置,将Diffuse和Lightmap的压缩改成"无"或"高质量"。

法线贴图的纹理类型改成"法线贴图"。除颜色图外其它贴图全部取消勾选sRGB。

ramp图将压缩改成"无"或"高质量",关闭生成Mip,贴图拼接模式改成"钳制"。

FaceLightmap将压缩改成"无"或"高质量",并关闭生成Mip。最后的设置如下图所示:

贴图设置

如果最终的渲染结果,出现贴图不清晰,有锯齿,有杂色,颜色深浅不对等问题,那大概率是贴图设置有问题,建议先重新认真检查一遍贴图设置。

OK,贴图处理完成后开始写Shader。在正式开始前先做一些准备工作。

原神的角色实际上应该是渲染了三遍,第一遍用第一套UV渲染正面,第二遍用第二套UV渲染背面,第三遍渲染描边,这样可以做到单面片正反两面的贴图不一样,崩铁应该也是使用了这套技术。

正面,注意裙摆的uv位置和unity中的效果

背面,注意裙摆的uv位置和unity中的效果

———————————————————————————————————————

RendererFeatures

URP使用多pass需要使用RendererFeatures,先设置一下RendererFeatures。在项目大纲找到UniversalRenderer,默认路径在Assets/Settings/UniversalRenderer。

默认路径在Assets/Settings/UniversalRenderer

然后在检查器找到RendererFeatures,点击Add RendererFeature添加一个RenderObjects,然后在LightModeTags添加三个元素,三个元素分别为“渲染正面pass、渲染背面pass和渲染描边pass”。

元素0代表正面pass,元素1代表背面pass,元素2代表描边pass

然后在LayerMask(图层蒙版)把角色对应的图层勾上,默认在Default层,勾上Everything会所有图层都显示。

LayerMask勾上Everything

———————————————————————————————————————

Shader准备工作

正式开始前先把计算需要用到的变量和向量等数据准备好,先来准备面板参数,面板参数如下:

Properties {
//面板参数
[Space(20.0)]
[Toggle]_genshinShader( "是否是脸部" , float) = 0.0

[Space(15.0)]
[NoScaleOffset]_diffuse( "Diffuse" , 2d) = "white"{}
_fresnel( "边缘光范围" , Range(0.0, 10.0)) = 1.7
_edgeLight( "边缘光强度" , Range(0.0, 1.0)) = 0.02
[Space(8.0)]
_diffuseA( "Alpha(1透明, 2自发光)" , Range(0.0, 2.0)) = 0
_Cutoff( "透明阈值" , Range(0.0, 1.0)) = 1.0
[HDR]_glow( "自发光强度" , color) = (1.0, 1.0, 1.0, 1.0)
_flicker( "发光闪烁速度" , float) = 0.8
[Space(30.0)]

[NoScaleOffset]_lightmap( "Lightmap/FaceLightmap" , 2d) = "white"{}
_bright( "亮面范围" , float) = 0.99
_grey( "灰面范围" , float) = 1.14
_dark( "暗面范围" , float) = 0.5
[Space(30.0)]

[NoScaleOffset]_bumpMap( "Normalmap" , 2d) = "bump"{}
_bumpScale( "法线强度" , float) = 1.0
[Space(30.0)]

[NoScaleOffset]_ramp( "Shadow_Ramp" , 2d) = "white"{}
[Toggle]_dayAndNight("是否是白天" , float) = 0.0
[Space(8.0)]
_lightmapA0("1.0_Ramp条数" , Range(1, 5)) = 1
_lightmapA1("0.7_Ramp条数" , Range(1, 5)) = 4
_lightmapA2("0.5_Ramp条数" , Range(1, 5)) = 3
_lightmapA3("0.3_Ramp条数" , Range(1, 5)) = 5
_lightmapA4("0.0_Ramp条数" , Range(1, 5)) = 2
[Space(30.0)]

[NoScaleOffset]_metalMap( "MetalMap" , 2d) = "white"{}
_gloss( "高光范围" , Range(1, 256.0)) = 1
_glossStrength( "高光强度" , Range(0.0, 1.0)) = 1
_metalMapColor( "金属反射颜色" , color) = (1.0, 1.0, 1.0, 1.0)
[Space(30.0)]

_outline( "描边粗细" , Range(0.0, 1.0)) = 0.4
_outlineColor0( "描边颜色1" , color) = (1.0, 0.0, 0.0, 0.0)
_outlineColor1( "描边颜色2" , color) = (0.0, 1.0, 0.0, 0.0)
_outlineColor2( "描边颜色3" , color) = (0.0, 0.0, 1.0, 0.0)
_outlineColor3( "描边颜色4" , color) = (1.0, 1.0, 0.0, 0.0)
_outlineColor4( "描边颜色5" , color) = (0.5, 0.0, 1.0, 0.0)
}

URP的声明参数必须包含在CBUFFER_START(UnityPerMaterial)和CBUFFER_END之间,我们把这些代码和声明贴图的代码都用HLSLINCLUDE和ENDHLSL代码块包起来,之后写的方法也放在这里面,这样后面就不用在每个pass都声明一次了。

HLSLINCLUDE
//导入库
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" //默认库
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" //光照库
CBUFFER_START(UnityPerMaterial) //常量缓冲区开头
//声明面板参数
float _genshinShader; //是否是脸部
//diffuse
float _fresnel; //边缘光范围
float _edgeLight; //边缘光强度
float _diffuseA; //diffuseA
float _Cutoff; //透明阈值
float4 _glow; //自发光强度
float _flicker; //发光闪烁速度
//lightmap/FaceLightmap
float _bright; //亮面范围
float _grey; //灰面范围
float _dark; //暗面范围
//normal
float _bumpScale; //法线强度
//ramp
float _dayAndNight; //是否是白天
float _lightmapA0; //1.0_Ramp条数
float _lightmapA1; //0.7_Ramp条数
float _lightmapA2; //0.5_Ramp条数
float _lightmapA3; //0.3_Ramp条数
float _lightmapA4; //0.0_Ramp条数
//高光
float _gloss; //高光范围
float _glossStrength; //高光强度
float3 _metalMapColor; //金属折射颜色
//描边
float _outline; //描边粗细
float3 _outlineColor0; //描边颜色1
float3 _outlineColor1; //描边颜色2
float3 _outlineColor2; //描边颜色3
float3 _outlineColor3; //描边颜色4
float3 _outlineColor4; //描边颜色5
CBUFFER_END //常量缓冲区结尾
//声明贴图
TEXTURE2D(_diffuse); //Diffuse
SAMPLER(sampler_diffuse);
TEXTURE2D(_lightmap); //Lightmap/FaceLightmap
SAMPLER(sampler_lightmap);
TEXTURE2D(_bumpMap); //Normal
SAMPLER(sampler_bumpMap);
TEXTURE2D(_ramp); //Shadow_Ramp
SAMPLER(sampler_ramp);
TEXTURE2D(_metalMap); //MetalMap
SAMPLER(sampler_metalMap);
ENDHLSL

———————————————————————————————————————

正面Pass

然后先写第一个渲染正面的pass,第一个pass的LightMode标签,设置成之前在RendererFeatures创建的三个元素中的第一个。

//渲染正面
Pass { //pass语义段
Tags { "LightMode" = "head" } //渲染标签

然后输入结构获取需要的数据。

//输入结构
struct a2v {
float4 vertex : POSITION; //获取顶点数据
float2 texcoord0 : TEXCOORD0; //获取uv0
float3 normal : NORMAL; //获取顶点法线
float4 tangent : TANGENT; //获取顶点切线
};

输出结构定义一个4维矩阵存放数据,以充分利用插值寄存器。

//输出结构
struct v2f {
float4 pos : SV_POSITION; //顶点数据
float2 uv0 : TEXCOORD0; //uv0
//矩阵
float4 TtoW0 : TEXCOORD1; //x切线,y副切线,z法线,w顶点
float4 TtoW1 : TEXCOORD2; //x切线,y副切线,z法线,w顶点
float4 TtoW2 : TEXCOORD3; //x切线,y副切线,z法线,w顶点
};

在顶点Shader将需要的数据传递给片元Shader,矩阵的xyzw分别存放切线,副切线,法线与顶点。

//顶点Shader
v2f vert (a2v v) {
v2f o; //定义返回值
o.pos = TransformObjectToHClip(v.vertex.xyz); //MVP变换(模型空间>>世界空间>>视觉空间>>裁剪空间)
o.uv0 = v.texcoord0; //传递uv0(无变换)
float3 nDirWS = TransformObjectToWorldNormal(v.normal); //世界空间法线
float3 tDirWS = TransformObjectToWorld(v.tangent.xyz); //世界空间切线
float3 bDirWS = cross(nDirWS, tDirWS) * v.tangent.w; //世界空间副切线
float3 posWS = TransformObjectToWorld(v.vertex.xyz); //世界顶点位置
//构建矩阵
o.TtoW0 = float4(tDirWS.x, bDirWS.x, nDirWS.x, posWS.x); //x切线,y副切线,z法线,w顶点
o.TtoW1 = float4(tDirWS.y, bDirWS.y, nDirWS.y, posWS.y); //x切线,y副切线,z法线,w顶点
o.TtoW2 = float4(tDirWS.z, bDirWS.z, nDirWS.z, posWS.z); //x切线,y副切线,z法线,w顶点
return o; //返回顶点Shader
}

在片元Shader将需要用到的贴图采样出来,法线贴图处理好,要用到的向量准备好,要用到的点乘结果准备好。

//片元Shader
half4 frag (v2f i) : SV_TARGET {
//采样贴图
float3 baseColor = SAMPLE_TEXTURE2D(_diffuse, sampler_diffuse, i.uv0).rgb; //diffuseRGB通道
float diffuseA = SAMPLE_TEXTURE2D(_diffuse,sampler_diffuse, i.uv0).a; //diffuseA通道
float4 lightmap = SAMPLE_TEXTURE2D(_lightmap, sampler_lightmap, i.uv0).rgba; //lightmap
//法线贴图
float3 nDirTS = UnpackNormal(SAMPLE_TEXTURE2D(_bumpMap, sampler_bumpMap, i.uv0)).rgb; //切线空间法线(采样法线贴图并解码)
nDirTS.xy *= _bumpScale; //法线强度
nDirTS.z = sqrt(1.0 - saturate(dot(nDirTS.xy, nDirTS.xy))); //计算法线z分量
//准备向量
float3 posWS = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w); //世界空间顶点
//切线空间法线转世界空间法线
float3 nDirWS = normalize(half3(dot(i.TtoW0.xyz, nDirTS), dot(i.TtoW1.xyz, nDirTS), dot(i.TtoW2.xyz, nDirTS)));
Light mlight = GetMainLight(); //光源
float3 lDirWS= normalize(mlight.direction); //世界光源方向(平行光)
float3 vDirWS = normalize(_WorldSpaceCameraPos.xyz - posWS.xyz); //世界观察方向
float3 nDirVS = normalize(mul((float3x3)UNITY_MATRIX_V, nDirWS)); //世界空间法线转观察空间法线
float3 hDirWS = normalize(vDirWS + lDirWS) ; //半角方向
//向量点乘
float NdotL = dot(nDirWS, lDirWS); //兰伯特
float NdotH = dot(nDirWS, hDirWS); //Blinn-Phong
float NdotV = dot(nDirWS, vDirWS); //菲涅尔

float3 col = float3(0.0, 0.0, 0.0);

return half4(col, 1.0); //输出
}

好的,准备工作完成,可以开始愉快的计算了。先开始计算身体的结果。

在计算前,我们看一下最终的混合分别需要什么: 漫反射(半Lambet) + 高光(BlinnPhong) + 金属(MatCap) + 边缘光(菲涅尔) + 自发光 + 后处理(Bloom、ToneMapping、ColorAdjustments)。 那我们就按这个顺序来一一实现一下。

———————————————————————————————————————

漫反射

好的,那我们就先从漫反射开始计算,原神角色的漫反射,最重要的是ramp的部分,先看一下原神的ramp图。

ramp图共十条颜色,上面五条白天,下面五条晚上,芙芙的这张ramp第五条是空白的,实际只有四条颜色。

芙芙头发的ramp图也是上下共十条颜色,第五条空白,实际只有四条颜色。

原神的ramp一共十条颜色,上面五条代表白天,下面五条代表晚上,那我们怎样采样这张ramp呢?先看代码:

//ramp
float3 shadow_ramp(float4 lightmap, float NdotL){
lightmap.g = smoothstep(0.2, 0.3, lightmap.g); //lightmap.g
float halfLambert = smoothstep(0.0, _grey, NdotL + _dark) * lightmap.g; //半Lambert
float brightMask = step(_bright, halfLambert); //亮面
//判断白天与夜晚
float rampSampling = 0.0;
if(_dayAndNight == 0){rampSampling = 0.5;}
//计算ramp采样条数
float ramp0 = _lightmapA0 * -0.1 + 1.05 - rampSampling; //0.95
float ramp1 = _lightmapA1 * -0.1 + 1.05 - rampSampling; //0.65
float ramp2 = _lightmapA2 * -0.1 + 1.05 - rampSampling; //0.75
float ramp3 = _lightmapA3 * -0.1 + 1.05 - rampSampling; //0.55
float ramp4 = _lightmapA4 * -0.1 + 1.05 - rampSampling; //0.45
//分离lightmap.a各材质
float lightmapA2 = step(0.25, lightmap.a); //0.3
float lightmapA3 = step(0.45, lightmap.a); //0.5
float lightmapA4 = step(0.65, lightmap.a); //0.7
float lightmapA5 = step(0.95, lightmap.a); //1.0
//重组lightmap.a
float rampV = ramp0; //0.0
rampV = lerp(rampV, ramp1, lightmapA2); //0.3
rampV = lerp(rampV, ramp2, lightmapA3); //0.5
rampV = lerp(rampV, ramp3, lightmapA4); //0.7
rampV = lerp(rampV, ramp4, lightmapA5); //1.0
//采样ramp
float3 ramp = SAMPLE_TEXTURE2D(_ramp, sampler_ramp, float2(halfLambert, rampV));
float3 shadowRamp = lerp(ramp, halfLambert, brightMask); //遮罩亮面
return shadowRamp; //输出结果
}

下面讲一下我计算ramp采样的思路。我们知道采样贴图需要一个2维的uv向量,一般采样ramp会采用u轴用halfLambert,v轴给一个数值来采样。我们先来处理halfLambert。

lightmap.g = smoothstep(0.2, 0.3, lightmap.g);  //lightmap.g
float halfLambert = smoothstep(0.0, _grey, NdotL + _dark) * lightmap.g; //半Lambert

lightmap图的g通道存储了模型的AO,我们先smoothstep一下,得到黑白的AO图。

左为lightmap.g结果,右为smoothstep后的结果

然后把NdotL用smoothstep来处理一下,让亮面更大一些和暗部更通透一点。然后乘以AO,结果如下:

halfLambert结果

然后就可以用halfLambert来采样ramp了。但我们还缺少一个v轴,那怎样让v轴按这十条颜色来采样呢?这就需要用到lightmap图了,lightmap图的a通道存放了5种材质的id,分别是0.0,0.3,0.5,0.7,1.0。不过芙芙只有4种。lightmap.a如下图所示:

lightmap.a通道存放了5种材质的id,分别是0.0,0.3,0.5,0.7,1.0(芙芙只有4种)

以芙芙的头发材质为例,只看ramp图的上半部分,可以发现材质id的数量与ramp图的条数是对应数量的。

芙芙头发的lightmap.a通道只有4种材质,没有0.7

所以我们可以直接用lightmap图的a通道当作v轴去采样ramp,不过现在有一个问题,可以发现,芙芙皮肤部分的材质id是白色的,也就是1.0。但ramp图上皮肤的渐变在第二条,也就是0.7的位置,所以我们不能直接用lightmap图来采样,需要处理一下。经过观察,ramp图实际的采样顺序应该是1,4,3,5,2。

首先先开放一个参数来判断是白天还是晚上,如果白天则为0,晚上则为0.5。也可以把这个判断直接绑定到灯光上,用灯光的旋转角度改变白天和晚上,我这里是没有绑定灯光的,而是开放了一个滑条开关。

//判断白天与夜晚
float rampSampling = 0.0;
if(_dayAndNight == 0){rampSampling = 0.5;}

然后开放5个参数在面板上,方便美术调节。并把面板上1-5的采样条数转换成ramp图的取值范围。

//计算ramp采样条数
float ramp0 = _lightmapA0 * -0.1 + 1.05 - rampSampling; //0.95
float ramp1 = _lightmapA1 * -0.1 + 1.05 - rampSampling; //0.65
float ramp2 = _lightmapA2 * -0.1 + 1.05 - rampSampling; //0.75
float ramp3 = _lightmapA3 * -0.1 + 1.05 - rampSampling; //0.55
float ramp4 = _lightmapA4 * -0.1 + 1.05 - rampSampling; //0.45

这样,如果是白天,则是采样到0.55-0.95,而晚上则是采样到0.05-0.45(加0.05是为了防止采样到边缘)。

然后用step将lightmap.a的各个材质分离出来。

//分离lightmap.a各材质
float lightmapA2 = step(0.25, lightmap.a); //0.3
float lightmapA3 = step(0.45, lightmap.a); //0.5
float lightmapA4 = step(0.65, lightmap.a); //0.7
float lightmapA5 = step(0.95, lightmap.a); //1.0

并用刚才计算的ramp图取值范围来重组lightmap的a通道。

//重组lightmap.a
float rampV = ramp0; //0.0
rampV = lerp(rampV, ramp1, lightmapA2); //0.3
rampV = lerp(rampV, ramp2, lightmapA3); //0.5
rampV = lerp(rampV, ramp3, lightmapA4); //0.7
rampV = lerp(rampV, ramp4, lightmapA5); //1.0

得到的结果如下。这部分的计算对于美术同学可能有点难以理解,记住我一开始说的, 需要从美术思维转变到数学思维,只有这样才能理解代码上的加减乘除有什么意义。

左为白天,右为晚上

然后我们用得到的这个结果,配合halfLambert来采样ramp图。

//采样ramp
float3 ramp = SAMPLE_TEXTURE2D(_ramp, sampler_ramp, float2(halfLambert, rampV));

然后就可以得到这样的一个结果。

ramp最终采样结果

我们需要将亮面遮罩出来,用step来处理halfLambert分出亮面和暗面。

float brightMask = step(_bright, halfLambert);  //亮面

再用lerp遮罩ramp和halfLambert。

float3 shadowRamp = lerp(ramp, halfLambert, brightMask);  //遮罩亮面

左为step半兰伯特的结果,右为最后lerp的结果

至此,ramp的采样就结束了,最后将lerp的结果乘以baseColor就可以了,我们来看一下和游戏里的细节对比。

左边为游戏中效果,右边为实现效果

———————————————————————————————————————

高光

接下来是高光的计算,高光的计算相对简单,高光使用Blinn-Phong光照模型,先上代码:

//高光
float3 Spec(float NdotL, float NdotH, float3 nDirVS, float4 lightmap, float3 baseColor){
float blinnPhong = pow(max(0.0, NdotH), _gloss); //Blinn-Phong
float3 specular = blinnPhong * lightmap.r * _glossStrength; //高光强度
specular = specular * lightmap.b; //混合高光细节
specular = baseColor * specular; //叠加固有色
lightmap.g = smoothstep(0.2, 0.3, lightmap.g); //lightmap.g
float halfLambert = smoothstep(0.0, _grey, NdotL + _dark) * lightmap.g; //半Lambert
float brightMask = step(_bright, halfLambert); //亮面
specular = specular * brightMask; //遮罩暗面
return specular; //输出结果
}

lightmap的r通道存放了模型的高光度,b通道存放了模型的高光细节。

左为lightmap.r, 右为lightmap.b

用Blinn-Phong乘以lightmap.r再乘以lightmap.b,得到高光的形状。

float blinnPhong = pow(max(0.0, NdotH), _gloss);  //Blinn-Phong
float3 specular = blinnPhong * lightmap.r * _glossStrength; //高光强度
specular = specular * lightmap.b; //混合高光细节

原神里的角色,高光部分是有固有色的,所以我们还要乘以baseColor。

specular = baseColor * specular;  //叠加固有色

然后还可以发现,原神中角色处于暗部的部分是没有高光的,所以我们还要step一个halfLambert来遮罩亮暗部分。halfLambert的处理方式和漫反射的一样。

游戏中人物亮面高光带固有色,暗面无高光
lightmap.g = smoothstep(0.2, 0.3, lightmap.g);  //lightmap.g
float halfLambert = smoothstep(0.0, _grey, NdotL + _dark) * lightmap.g; //半Lambert
float brightMask = step(_bright, halfLambert); //亮面
specular = specular * brightMask; //遮罩暗面

最终实现的高光部分效果。

高光实现效果

———————————————————————————————————————

金属

然后来处理金属,金属部分用的是MatCap采样方法,这是原神的metalMap图:

原神的metalMap图

上代码:

//金属
float3 Metal(float3 nDirVS, float4 lightmap, float3 baseColor){
float metalMask = 1 - step(lightmap.r, 0.9); //金属遮罩
//采样metalMap
float3 metalMap = SAMPLE_TEXTURE2D(_metalMap, sampler_metalMap, nDirVS.rg * 0.5 + 0.5).r;
metalMap = lerp(_metalMapColor, baseColor, metalMap); //金属反射颜色
metalMap = lerp(0.0, metalMap, metalMask); //遮罩非金属区域
return metalMap; //输出结果
}

先将金属部分提取出来。然后用观察空间法线的rg通道采样metalMap贴图。游戏里的金属的边缘会反射一个类似环境色的颜色。我们给一个颜色,和baseColor用metalMap来lerp一下,然后再将金属遮罩出来,金属部分就完成了。来看一下和游戏里的效果对比:

左为游戏中效果,右为实现效果

———————————————————————————————————————

边缘光

原神中用的是屏幕深度边缘光,我是用菲涅耳实现的效果。代码非常简单,就是简单的菲涅尔用step卡出硬边,再乘baseColor即可,直接看代码就行了。

//边缘光
float3 edgeLight(float NdotV, float3 baseColor){
float3 fresnel = pow(1 - NdotV, _fresnel); //菲涅尔范围
fresnel = step(0.5, fresnel) * _edgeLight * baseColor; //边缘光强度
return metal; //输出结果
}

边缘光实现效果:

边缘光效果

———————————————————————————————————————

自发光

//自发光
float3 light(float3 baseColor, float diffsueA){
diffsueA = smoothstep(0.0, 1.0, diffsueA); //去除噪点
float3 glow = lerp(0.0, baseColor * (sin(_Time.w * _flicker) + _glow), diffsueA); //自发光
return glow; //输出结果
}

diffuse的a通道存放的是自发光遮罩(也有可能是透明遮罩),不过这个遮罩有很多奇怪的噪点,我smoothstep了一下,把噪点去除了,然后用漫反射乘以HDR强度即可。自发光我还添加了闪烁的效果。

左为默认diffuse.a,有不少噪点, 右为smoothstep后的diffsue.a

———————————————————————————————————————

最终混合

我们将目前得到的所有效果相加即可(自发光暂时不用,后面还要判断a通道是自发光还是透明蒙版)。

//身体






请到「今天看啥」查看全文


推荐文章
视觉志  ·  我不上班你养我啊?
8 年前
癌图腾  ·  酒与健康,真实的谎言?
7 年前