当前位置: 首页 > 手游 > 原神

《原神》卡渲效果逆向还原 【五、传送锚点全息效果】

来源:网络 时间:2023-08-01 09:52:41
导读前言:断断续续一直在研究怎样更快更好地完成卡通风格渲染的制作流程,看到《原神》中满满的卡渲细节处理和制作技巧,更是想深入探索其背后的制作流程和实现原理。由于笔者的认知程度和专业水平有限,几乎所有的内…

前言:断断续续一直在研究怎样更快更好地完成卡通风格渲染的制作流程,看到《原神》中满满的卡渲细节处理和制作技巧,更是想深入探索其背后的制作流程和实现原理。由于笔者的认知程度和专业水平有限,几乎所有的内容都是自己根据游戏中实际的观察摸索来研究,同时参考了前辈们的很多文章和视频教程。文章中难免会出现很多漏洞、错误,还有很多问题会有已有的更优解决方案,还请各位前辈们多多指正。

很久没有更新过文章了。这段时间遇到了难产的瓶颈期。想用虚幻引擎挑战更高难度的效果,尝试了天云峠雷暴、渊下宫入口、自动检测边缘并交互的瀑布和喷泉等效果,但是都由于各种技术原因(复杂模型的半透排序等),只完成了部分效果,所以一直没有完完整整写出文章。这几个月也一直在断断续续地学习HLSL,我想不如换个方式挑战一下自己,尝试自己独立用Unity的URP管线去手写一些HLSL的Shader,结合我在学习HLSL中的一些方法,与大家共同分享。这是第一次,尝试写一个原神中传送锚点的全息效果。

1.效果分析

还是先上图片:

这是一个通用的Shader,可以看到无论是尘歌壶中的『外景锚点』还是大世界中的『口袋锚点』,他们基本的样子是一致的,所以Shader就应该保持其普适性。特殊的部分则通过特效部分来加以区分(例如上右图口袋锚点中闪闪发光的小星星)。

从模型和效果观察,整个传送锚点分为两部分:锚点主体(中心)和光束(边缘的环绕面片)

①、锚点主体:半透明,边缘菲涅尔,流动的菲涅尔效果

②、光束:半透明,边缘柔滑(边缘菲涅尔),上升的粒子斑点,光束和斑点自下而上渐弱

下面就来单独实现这两部分

2、锚点主体

2.1 建模

全息的传送锚点不好看出其模型的完整结构,但是游戏中还有一个相同的模型:

通过这个参考,快速地就能在Maya中建出来模型

这个模型的UV不需要进行特殊处理,在后面的Shader中不会用到。

2.2 ASE效果还原

在写Shader之前,我先用ASE的节点连了一遍,整理了一下思路。在这里我的做法是尽可能不使用封装好的材质函数(例如Fresnel),而是用最基本的能拿到的节点去连出这些算法,这样在后面用HLSL写Shader的时候会更加清晰。

①、菲涅尔效果

用ViewDirection和模型的WorldNormal去做点乘实现边缘的菲涅尔效果,加入Power参数来控制强度

②、菲涅尔流动效果

这里通过采样一张不断偏移的噪波贴图,将得到的数据与刚刚的菲涅尔效果相乘完成制作

在这里由于菲涅尔效果与模型UV无关,所以我提取出世界位置的X和Y作为一个新的UV去采样这张噪波贴图,解决了菲涅尔区域对这张贴图的采样问题。另外,关于UV的Tilling和Offset没有用封装的函数去做,把所有的算法写开,方便在后面手写Shader的时候理顺思路。

用SubstanceDesigner简单地制作一张合适的贴图:

③、合并

最后将刚刚的两部分合并起来,作为半透明通道,再加入HDR的颜色

实现这个简单的效果

2.3 用HLSL重写Shader

ASE的节点连连看是一个从前到后的过程,我们大部分时间精力都在实现结果上,而不会去考虑准备问题,也就相当于片元着色阶段。在ASE中我们可以直接拿到各种想要的空间信息、函数等,然后通过从前往后线性的思维,直接连出想要的效果。

与ASE不同的是,手写Shader是一个部分逆向的思维:

“我要实现什么效果?我要如何实现这些效果?”这是在片元着色器中要考虑的问题,是节点连连看中的工作重心,也是我们在还原效果中最先会想到的。

“为此我应该做什么准备?”等涉及到的例如空间变换的问题,这是在顶点着色器及顶点着色器到片元着色器的过程中要考虑的。

而Shader的执行顺序是先顶点着色器→片元着色器,也就是很多想拿到的数据可能需要提前做好一系列的变换工作。

因此我们要实现一个思想过程上的转变。在实现效果的时候去思考这样几个问题:“这些数据能不能直接拿到?需不需要做空间变换?需不需要经过顶点着色的过程?”

在用ASE做完连连看以后,我们还可以将其直接编译出代码形式,虽然有些乱,但是部分内容也可以作为之后的参考

下面开始用HLSL写这个Shader效果:

首先是第一步,各种属性的定义,把ASE中定义为参数属性的变量全部写上(这一部分可以直接在ASE编译的代码中粘过来)

Properties { [HDR]_Color("Color", Color) = (1,0,0,0) _NoiseTex("NoiseTex", 2D) = "white" {} _WSuvTilling("WSuvTilling", Float) = 1 _Speed("Speed", Float) = 1 _TillingY("TillingY", Float) = 1 _TillingX("TillingX", Float) = 1 _NoisePower("MaskPower", Float) = 1 _FresnelPower("FresnelPower", Float) = 1 }

下面开始写SubShader中的内容

由于是半透明,所以RenderType和Queue一定要选择半透明所对应的

Tags { "RenderPipeline"="UniversalPipeline" "RenderType"="Transparent" "Queue"="Transparent" }

然后开始写这个Pass

代码头的各种标记:

Name "Forward" Tags { "LightMode"="UniversalForward" } Cull Back Blend SrcAlpha OneMinusSrcAlpha, One OneMinusSrcAlpha ZWrite Off ZTest LEqual Offset 0 , 0 ColorMask RGBA

声明顶点着色器和片元着色器的名字

HLSLPROGRAM #pragma vertex vert #pragma fragment frag

引用各个库的内容

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

在CBUFF中重新声明一开始定义的属性参数变量

CBUFFER_START(UnityPerMaterial) float4 _Color; float _WSuvTilling; float _Speed; float _TillingX; float _TillingY; float _NoisePower; float _FresnelPower; CBUFFER_END

定义主纹理和采样器

TEXTURE2D(_NoiseTex); SAMPLER(sampler_NoiseTex);

在这个Shader效果中,由于顶点着色器中的顶点信息是ObjectPosition下的,所以不能够直接拿到的部分是WorldPosition和WorldNormal,所以在定义Attributes和Varyings结构体中就要加入相关内容,并且在顶点着色器中将这些需要的内容转换完成:

struct Attributes { float4 positionOS : POSITION; float3 normal : NORMAL; }; struct Varyings { float4 positionCS : SV_POSITION; float3 positionWS : TEXCOORD1; float3 normalWS : TEXCOORD2; }; Varyings vert (Attributes v) { Varyings o = (Varyings)0; o.positionCS = TransformObjectToHClip(v.positionOS.xyz); o.positionWS = TransformObjectToWorld(v.positionOS.xyz); o.normalWS = TransformObjectToWorldNormal(v.normal); return o; }

下面就是在片元着色器中复原刚刚ASE连连看中的内容,可以直接对照着自己的ASE连连看把过程写下来了。

菲涅尔效果:

//Fresnel half3 worldPosition = i.positionWS; half3 viewDirectionWS = normalize(GetCameraPositionWS() - i.positionWS); half fresnel = pow(1 - saturate(dot(viewDirectionWS, i.normalWS)), _FresnelPower);

菲涅尔部分的噪波流动效果:

//WorldPosition UV Tilling And Offset half2 worldPosUV = (half2(worldPosition.x, worldPosition.y)) / _WSuvTilling; half2 texTilling = (half2(_TillingX, _TillingY)); half2 texSpeed = (half2(0, _Speed * _TimeParameters.x)); half2 texUV = worldPosUV * texTilling + texSpeed; //Sample Texture half4 mainTex = SAMPLE_TEXTURE2D(_NoiseTex, sampler_NoiseTex, texUV);

合并输出

//Alpha half alpha = (1 - saturate(pow(saturate(mainTex.x), _NoisePower))) * fresnel; return half4(_Color.xyz, alpha);

整个过程就结束了。

最终的效果:

3.光束

3.1 建模

光束用一个去掉上下底面的圆柱代替即可,注意UV要完全展开充满整个第一象限,顶部底部应与模型方向保持一致

3.2 ASE效果还原

实现方式与上面的锚点Shader类似

①、光束柔软效果

这里为了实现光束边缘柔软的效果,同样使用菲涅尔的操作对边缘进行柔滑。

由于自下而上光束强度逐渐减弱,因此通过拿到UV的V方向作为遮罩去控制渐弱程度。

②、星星斑点粒子上升效果

单独对模型UV的V方向做一个持续的偏移,采样一张星星点点的贴图,实现效果

同样星星斑点自下而上强度逐渐减弱,因此通过拿到UV的V方向作为遮罩去控制渐弱程度

③、合并

最后将两部分相加,作为半透明通道去使用,再单独连一个HDR颜色完成制作

3.3 用HLSL重写Shader

准备工作与锚点主体的相同,但是要在顶点着色器中加入

float2 uv :TEXCOORD0

也就是UV的变换,不然的话在片元着色器里拿不到模型的UV。

然后对应刚刚ASE连连看的过程,还原这个效果

half4 frag (Varyings i) : SV_Target { //1.Surface half3 viewDirectionWS = normalize(GetCameraPositionWS() - i.positionWS); half surfaceFresnel = pow(saturate(dot(viewDirectionWS, i.normalWS)), _SurFrePower) * _SurFreIntensity; half2 texUV = i.uv; half surfaceUVMaskY = pow(saturate((1- (half(i.uv.y)))), _SurTopMaskPower); half surfaceAlpha = surfaceFresnel * surfaceUVMaskY; //2.Star half2 starTilling = (half2(_StarTillingX, _StarTillingY)); half2 starSpeed = (half2(0, _StarSpeed * _TimeParameters.x)); half2 starUV = i.uv * starTilling + starSpeed; half4 starTex = SAMPLE_TEXTURE2D(_StarTex, sampler_StarTex, starUV); half starUVMaskY = pow(saturate((1- (half(i.uv.y)))), _StarTopMaskPower); half starAlpha = starTex.x * starUVMaskY; //3.Alpha and Return half Alpha = saturate(surfaceAlpha + starAlpha); return half4(_Color.xyz, Alpha); }

最终效果:

这是第一次写关于用Unity的URP管线手写HLSL Shader的分享,代码可能不是最优最简洁的。整个的制作过程也是我去思考手写和连连看区别的探索过程,希望能把这些自己的方法和想法分享出来,帮助到同样刚刚入门手写Shader的同学,与行业的前辈大佬们共同交流。

道阻且长,行则将至。希望各位前辈大佬们能够给予评价和指导!

最后的最后,春招已经开始了,祝愿大家都能够心想事成,好运连连,offer连连!

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:704559159@qq.com

Top
加盟网