图形引擎实战:镭射效果制作分享
镭射的效果可以拆解为以下几点:
- 边缘光
- 镭射效果
- 流动亮片效果
最终渲染效果
下面我来为大家介绍一下各个部分是如何实现的。
边缘光:
边缘光的实现方案是由常见的菲尼尔函数完成的。通过rimSoft和rimStrength两个变量分别对边缘光强度和边缘光过渡的软硬程度进行调控。最后在与边缘光颜色相乘即可。具体的代码实现如下:
float3 RimLighting(float3 V,float3 N,float rimStrength,float rimSoft,float3 rimColor)
{
float3 rimlight = (float3)0;
float fresnel = saturate(dot(V,N));
fresnel = rimStrength * pow(1.0 - fresnel,rimSoft);
fresnel = saturate(fresnel);
rimlight = fresnel * rimColor;
return rimlight;
}
边缘光设置面板
镭射效果:
镭射效果常见的制作方案有使用基于物理的薄膜干涉算法生成,可以参考HDRP中彩虹色(Iridescence)材质的算法。该方案的优势1.遵循物理的2.可以使用少量的参数去调控镭射的效果。缺点的话也有几点比如:镭射颜色不能够由美术自定义,算法计算较为复杂。另一个常见的方案是使用fresnel然后经过HSV的转换从而生成镭射颜色。计算相对简单,缺点仍是颜色不能够自定义,还有一种常见的方案是采样一张镭射贴图来模拟,好处是颜色可以自定义同时性能上还很高效没有复杂的公式计算。综上所述,我还是选择了可以自定义镭射颜色的贴图方案实现。
我使用了两种不同的贴图方案来实现镭射效果,第一种是使用手绘贴图实现,由美术同学手绘镭射颜色。另一种方案是采用实时生成Ramp图方案。这两个方案的好处是美术同学可以手动的去控制镭射的颜色。很方便去控制。接下来我便讲述一下我是如何去实现的吧。
手绘贴图方案GUI界面
第一种使用手绘贴图来模拟镭射的效果方案比较简单,我们只需创建两个向量来对镭射贴图进行平铺度和流动速度进行控制。然后利用NdotV和NdotL作为uv坐标对贴图进行采样,这时镭射效果会受到视角方向和光照方向的影响。具体代码实现如下:
float NdotV = dot(customLitData.N,customLitData.V);
float2 irideColor_uv = float2(NdotV,lerp(NdotV,NdotL,0.5));
irideColor_uv = max(0,irideColor_uv);
irideColor_uv = irideColor_uv * 0.5 + 0.5;
irideColor = SAMPLE_TEXTURE2D(_IrideScenceMap,sampler_IrideScenceMap,irideColor_uv);
iridescence = irideColor * _iridescenceIntensity;
Ramp图方案GUI界面
相比之前使用手绘图的方案,该Ramp图方案的优点在于镭射颜色可以在引擎内实时地进行控制和调整。该方案的重点是如何实时生成这张Ramp图,以便美术人员可以更好地调整效果。我查阅了网上普遍的方案,常见的做法是制作一个Ramp图生成工具,然后将生成好的ramp图赋给材质。而我将这个过程移植到了材质ShaderGUI中,这样就可以更方便地直接在材质中进行调控,而且看起来更简洁。也简化了操作步骤。
下图为ShaderGUI绘制Ramp图区域的函数,主要是利用了EditorGUILayouy.GradientField函数绘制Gradient到ShaderGUI上面。当用户修改了Gradient的内容时,会调用UpdateTex()函数来对Ramp图进行实时的更新。
private Texture2D UpdateTex(int width)
{
Texture2D tex = new Texture2D(width, 1, TextureFormat.RGBA32, false,false);
tex.wrapMode = TextureWrapMode.Repeat;
tex.filterMode = FilterMode.Bilinear;
tex.name = "RampTexture";
var colors = new Color[tex.width * tex.height];
for (int x = 0; x < width; ++x)
{
colors[x] = s_gradient.Evaluate(1.0f * x / (width - 1));
}
tex.SetPixels(colors);
tex.Apply();
return tex;
}
当调整好Ramp图颜色时,需要设置保存路径。并导出这张Ramp图。再ShaderGUI中使用GUILayout.Button()创建一个Button,当用户点击该按钮时调用SetTexturePath(),利用内置API EditorUtility.SaveFilePanelInProject来设置保存路径。具体的函数实现如下:
if (GUILayout.Button("设置保存路径"))
{
SetTexturePath();
}
private void SetTexturePath()
{
Var currentRampObjPath = AssetDatabase.GetAssetPath(m_MaterialEditor.serializedObject.targetObject);
var defaultDirectory = System.IO.Path.GetDirectoryName(currentRampObjPath);
s_texturePath = EditorUtility.SaveFilePanelInProject("Export as PNG file", "LightRamp", "png", "Set file path for the PNG file", defaultDirectory);
}
接下来时贴图导出部分实现,先判断当前保存路径是否为空,如果为空就需要用户进行贴图路径的设置。然后再将其导出。
最后就是Shader中对Ramp图的采样的部分,Ramp图的UV坐标是使用fresnel作为Ramp的U轴坐标,0.5作为V轴坐标。我们对fresnel添加了一些额外的操作,主要是对它进行偏移,重复度,强度控制,具体Shader实现如下:
float Iride_fresnel = saturate(dot(customLitData.V,customLitData.N));
Iride_fresnel = saturate(pow(mul(Iride_fresnel,_IrideAlphaSoft),_IrideAlphaStrength));
Iride_fresnel = 1 - Iride_fresnel;
Iride_fresnel *= _IrideRampTilling;
irideColor = SAMPLE_TEXTURE2D(_IrideRampMap,sampler_IrideRampMap,float2(Iride_fresnel,0.5));
iridescence = irideColor * _iridescenceIntensity;
通过调整ramp图可以很容易的定制自己想要的镭射效果:
流动的亮片效果:
(亮片效果图)
模拟亮片效果,通常可以使用贴图或噪声算法进行模拟。常用于亮片计算的噪声函数有冯洛诺伊图(Voronoi diagram), Simple Noise算法。如下图所示左边的为冯洛诺伊图,右边的是SimpleNoise。
我选择使用ShaderGraph中的Simple Noise节点来生成亮点效果。右键点击该节点并选择"Show Generated Code"选项,即可查看该节点的实现代码。我们可以将相关引用函数复制到我们的shader中。
(SimpleNoise函数实现)
拷贝完SimpleNoise函数后,我们可以对它进行调用。这个函数的使用方式很简单,只需要设置两个变量_SparkSpeed和_SparkTilling来控制噪声的流动速度和重复度。接下来,使用step函数对噪声进行裁剪,可以控制亮片的稀疏度,最终可以得到散落分布的亮片。
half2 ParallaxMapping(half3 viewTS,half3 normalTS,half IOR)
{
viewTS = normalize(viewTS);
normalTS = normalize(normalTS);
half2 uvOffset = (half2)0;
half3 refractVector = refract(viewTS,normalTS,IOR);
uvOffset = refractVector.xy/refractVector.z;
return uvOffset;
}
为了能够让亮片效果看起来更加的生动,可以让亮片的UV沿着折射方向进行一定的偏移。
float2 offset = ParallaxMapping(viewDirTS,normalTS,0.3);
亮片效果材质面板
到此镭射效果分享就结束啦!
欢迎加入我们!
感兴趣的同学可以投递简历至:CYouEngine@cyou-inc.com
#我的求职思考##搜狐畅游##引擎开发工程师##技术美术#