Unity Shader - 类似七龙珠的人物气焰效果

141 篇文章 34 订阅


环境

Unity : 2018.3.11f1
Pipeline : BRP


效果

请添加图片描述请添加图片描述请添加图片描述请添加图片描述

覆盖身前
请添加图片描述

覆盖身前 + 叠加混合
请添加图片描述

风格化 版本,更适合 NPR:
请添加图片描述

再优化一版本
请添加图片描述

请添加图片描述


该效果是自己摸索的(也是自己再国外社区逛的少,等后续将 小说 看完了,再开始刷效果。。。)

制作思路不复杂,做过 shell 描边(法线方向外扩)的同学都已经知道怎么回事

麻烦的是调整参数,需要花些时间来调整到心意的效果


思路


passes

  • pass 0 - toon shading
  • pass 1 - toon outline
  • pass 2 - power up 气焰效果(英文暂时不知道叫啥,这里暂时叫:power up)

在这里插入图片描述


pass - 气焰

思路:

FORWARD pass 先写入模板值:90
在这里插入图片描述

然后气焰不绘制 stencil 90 的部分
在这里插入图片描述

气焰 shader
思路写在了 shader 代码的注释中

//=================== power up START ===============================
struct appdata_powerup {
	float4 vertex : POSITION;
	half3 normal : NORMAL;
};
struct v2f_powerup {
	float4 pos : SV_POSITION;
	float3 normalWS : TEXCOORD0;
	float4 newPosWS : TEXCOORD2;
	float4 srcXY_NewXY : TEXCOORD4;
};
uniform half _PowerUpOutline;
uniform half _PowerUpRimPower;
uniform half4 _PowerUpOutlineColor1;
uniform half4 _PowerUpOutlineColor2;
uniform half _PowerUpNoiseScale_Alpha;
uniform half _PowerUpNoiseScale;
uniform half3 _PowerUpNoiseOffset;
uniform half3 _PowerUpSpeed_Alpha;
uniform half3 _PowerUpSpeed;
uniform half _PowerUpFadeOutDistance;
// uniform float2 _PowerUpGradientOffsetVertexEdge;
uniform half _PowerUpPingpongEdgeSizeFrequence;
uniform half _PowerUpPingpongEdgeSizeStrength;
uniform half _PowerUpVertexNoiseFrequence;
uniform half _PowerUpVertexNoiseStrength;

v2f_powerup vert_powerup(appdata_powerup v) {
	v2f_powerup o;
	// jave.lin : 计算 shell 扩展前的 世界坐标、裁剪坐标,为的是:计算原始的屏幕坐标
	// 原始世界坐标
	float4 srcPosWS = mul(unity_ObjectToWorld, v.vertex);
	// 原始裁剪坐标
	float4 srcPosCS = UnityWorldToClipPos(srcPosWS.xyz);
	// float edgeA = _PowerUpGradientOffsetVertexEdge.x;
	// float edgeB = _PowerUpGradientOffsetVertexEdge.y;
	// float outlineW = smoothstep(edgeA, edgeB, v.vertex.y);

	// jave.lin : pingpong 边缘大小【可选】,_PowerUpPingpongEdgeSizeFrequence 外头我暂时设置为0,也可以制作变体控制
	float pingpongOutlineW = sin(_Time.y * _PowerUpPingpongEdgeSizeFrequence);
	Unity_Remap_float4(pingpongOutlineW, half2(-1, 1), half2(0.5, 1), pingpongOutlineW);
	pingpongOutlineW *= _PowerUpPingpongEdgeSizeStrength;

	// jave.lin : shell 扩展
	v.vertex.xyz += v.normal.xyz * (pingpongOutlineW * _PowerUpOutline);

	// jave.lin : 顶点动画:这个是旧项目的模型,本来是作用在:v.vertex.y 的,但是模型制作不标准
	v.vertex.x += snoise((/*outlineW * */v.vertex.xyz + _Time.y) * _PowerUpVertexNoiseFrequence)
						* _PowerUpVertexNoiseStrength;
	// jave.lin : 计算新的 世界坐标、裁剪坐标,为的是:计算 shell 扩展后的屏幕坐标
	o.newPosWS = mul(unity_ObjectToWorld, v.vertex);
	o.pos = UnityWorldToClipPos(o.newPosWS.xyz);

	// jave.lin : 计算顶点法线 matrix_mIT
	o.normalWS = UnityObjectToWorldNormal(v.normal);

	// jave.lin : 根据 裁剪坐标,计算 shell 扩展前的原始SP,和 计算 shell 扩展后的 SP,(SP:screen position)
	float4 srcSP = ComputeScreenPos(srcPosCS);
	o.srcXY_NewXY.xy = srcSP.xy / srcSP.w;
	float4 newSP = ComputeScreenPos(o.pos);
	o.srcXY_NewXY.zw = newSP.xy / newSP.w;
	return o;
}
half4 frag_powerup(v2f_powerup i) : SV_Target{

	// jave.lin : 法线,如果为了节省性能,可以不做: normalize 计算,该 API 计算有点费
	half3 N = normalize(i.normalWS);
	// return half4(N, 1);

	// jave.lin : 计算 ViewDir
	half3 V = normalize(_WorldSpaceCameraPos.xyz - i.newPosWS.xyz);
	// return half4(V, 1);

	// jave.lin : 计算 rim / fresnel 的反向值
	half rimInv = dot(N, V);
	rimInv = pow(rimInv, _PowerUpRimPower);
	// return half4(rimInv.xxx, 1);

	// return half4(i.srcXY_NewXY.xy, 0, 1);
	// return half4(i.srcXY_NewXY.zw, 0, 1);

	// jave.lin : 计算 shell 扩展前的 原始 SP 和 扩展后的 SP 之间的距离
	float dis = distance(i.srcXY_NewXY.xy, i.srcXY_NewXY.zw);

	// jave.lin : 根据距离值做 fade out
	float fade = smoothstep(_PowerUpFadeOutDistance, 0, dis);
	// return half4(fade.xxx, 1);

	// jave.lin : 计算 alpha 的噪点抖动 的 3d noise 采用坐标的偏移值
	float3 moveVec_alpha = _Time.y * _PowerUpSpeed_Alpha;
	// float3 moveVec = _Time.y * _PowerUpSpeed;

	// jave.lin : 使用 3d noise 来采样,有点费,可以改用 2d noise,但只能使用 世界坐标xz,
	// 或是 屏幕坐标 来作为采样坐标,会导致多个角色的气焰叠加一起时,抖动完全一致的问题,但可以使用 noise offset 来处理,这里偷懒就不这么挣了
	// jave.lin : snoise 参考:Unity Shader - 搬砖日志 - 3D Noise, Noise 3D 相关
	// https://blog.csdn.net/linjf520/article/details/122342408
	float noise_alpha = snoise((i.newPosWS + moveVec_alpha) * _PowerUpNoiseScale_Alpha);
	// float noise_alpha1 = snoise((i.newPosWS + moveVec_alpha * noise_alpha) * _PowerUpNoiseScale_Alpha);
	// noise_alpha = max(noise_alpha, noise_alpha1);

	// jave.lin : 将 距离的淡出值应用上
	noise_alpha *= fade;

	// jave.lin : noise alpha 再次和 rim 反向之去一个最大值,为的是:尽量让靠经原始坐标的像素的 alpha 值大一些
	noise_alpha = max(noise_alpha, rimInv * fade);
	// float noise = snoise((i.newPosWS + moveVec + _PowerUpNoiseOffset) * _PowerUpNoiseScale);
	// return noise_alpha;
	
	// Unity_Remap_float4(noise, half2(0, 1), half2(0.5, 1), noise);
	
	// jave.lin : 使用 rim 反向值,来 混合两个颜色,color1 是气焰内色, color2 是气焰外色
	half3 tintCol = lerp(_PowerUpOutlineColor1.rgb, _PowerUpOutlineColor2.rgb, rimInv);
	half4 finalCol = half4(/*noise * */tintCol, noise_alpha);
	// jave.lin : TODO - 使用 rampTex 来映射气焰内外的着色
	// 后续添加上	

	// jave.lin : 夹一下,避免溢出
	finalCol = saturate(finalCol);

	return finalCol;
}
//=================== power up END ===============================

优化后的 shader

减少了比较多的计算量

//=================== power up START ===============================
struct appdata_powerup {
	float4 vertex : POSITION;
	half3 normal : NORMAL;
};
struct v2f_powerup {
	float4 pos : SV_POSITION;
	float3 normalWS : TEXCOORD0;
	float4 newPosWS : TEXCOORD2;
	float4 srcXY_NewXY : TEXCOORD4;
	float fadeNoise : TEXCOORD5; // jave.lin : 调试用
};
// jave.lin : 下面为了快速效果,暂时没有 pack 一下各个 uniform 的分量,后续可以优化
uniform half _PowerUpOutline;
uniform half _PowerUpRimPower;
sampler2D _PowerUpOutlineColorRampTex;
// uniform half _PowerUpNoiseScale_Alpha;
uniform half _PowerUpNoiseScale;
uniform half3 _PowerUpNoiseOffset;
uniform half3 _PowerUpSpeed_Alpha;
uniform half3 _PowerUpSpeed;
uniform half _PowerUpFadeOutDistance;
uniform float2 _PowerUpGradientOffsetVertexEdge;
uniform half _PowerUpPingpongEdgeSizeFrequence;
uniform half _PowerUpPingpongEdgeSizeStrength;
uniform half _PowerUpVertexNoiseFrequence;
uniform half _PowerUpVertexNoiseStrength;
uniform half2 _PowerUpVertexNoiseVerticalStrengthThreshold;

v2f_powerup vert_powerup(appdata_powerup v) {
	v2f_powerup o;
	// jave.lin : 计算 shell 扩展前的 世界坐标、裁剪坐标,为的是:计算原始的屏幕坐标
	// 原始世界坐标
	float4 srcPosWS = mul(unity_ObjectToWorld, v.vertex);
	// 原始裁剪坐标
	float4 srcPosCS = UnityWorldToClipPos(srcPosWS.xyz);

	// jave.lin : 纵向的 气焰强度控制

	half outlineW_EdgeA = _PowerUpVertexNoiseVerticalStrengthThreshold.x;
	half outlineW_EdgeB = _PowerUpVertexNoiseVerticalStrengthThreshold.y;

	half outlineW;
	Unity_Remap_float4(v.vertex.x, half2(outlineW_EdgeA, outlineW_EdgeB), half2(0, 1), outlineW);
	o.fadeNoise = outlineW;

	// jave.lin : pingpong 边缘大小【可选】,_PowerUpPingpongEdgeSizeFrequence 外头我暂时设置为0,也可以制作变体控制
	// float pingpongOutlineW = sin(_Time.y * _PowerUpPingpongEdgeSizeFrequence);
	// Unity_Remap_float4(pingpongOutlineW, half2(-1, 1), half2(0.5, 1), pingpongOutlineW);
	// pingpongOutlineW *= _PowerUpPingpongEdgeSizeStrength;

	// jave.lin : shell 扩展(挤出)强度
	half shellExtrudeIntensity =
		// pingpongOutlineW *
		_PowerUpOutline;

	// jave.lin : shell 扩展
	v.vertex.xyz += v.normal.xyz * shellExtrudeIntensity;

	// jave.lin : 顶点动画:这个是旧项目的模型,本来是作用在:v.vertex.y 的,但是模型制作不标准
	// jave.lin : snoise 参考:Unity Shader - 搬砖日志 - 3D Noise, Noise 3D 相关
	// https://blog.csdn.net/linjf520/article/details/122342408
	v.vertex.x += snoise((v.vertex.xyz + _Time.y) * _PowerUpVertexNoiseFrequence)
						* _PowerUpVertexNoiseStrength * outlineW;

	// jave.lin : 计算新的 世界坐标、裁剪坐标,为的是:计算 shell 扩展后的屏幕坐标
	o.newPosWS = mul(unity_ObjectToWorld, v.vertex);
	o.pos = UnityWorldToClipPos(o.newPosWS.xyz);

	// jave.lin : 计算顶点法线 matrix_mIT
	o.normalWS = UnityObjectToWorldNormal(v.normal);

	// jave.lin : 根据 裁剪坐标,计算 shell 扩展前的原始SP,和 计算 shell 扩展后的 SP,(SP:screen position)
	float4 srcSP = ComputeScreenPos(srcPosCS);
	o.srcXY_NewXY.xy = srcSP.xy / srcSP.w;
	float4 newSP = ComputeScreenPos(o.pos);
	o.srcXY_NewXY.zw = newSP.xy / newSP.w;
	return o;
}
half4 frag_powerup(v2f_powerup i) : SV_Target{
	// return half4(i.fadeNoise.xxx, 1);
	// jave.lin : 法线,如果为了节省性能,可以不做: normalize 计算,该 API 计算有点费
	// half3 N = normalize(i.normalWS);
	// half3 N = i.normalWS;
	// return half4(N, 1);

	// jave.lin : 计算 ViewDir
	half3 V = normalize(_WorldSpaceCameraPos.xyz - i.newPosWS.xyz);
	// return half4(V, 1);

	// jave.lin : 计算 rim / fresnel 的反向值
	// half rimInv = dot(N, V);
	half rimInv = dot(i.normalWS, V);
	rimInv = pow(rimInv, _PowerUpRimPower);
	// return half4(rimInv.xxx, 1);

	// return half4(i.srcXY_NewXY.xy, 0, 1);
	// return half4(i.srcXY_NewXY.zw, 0, 1);

	// jave.lin : 计算 shell 扩展前的 原始 SP 和 扩展后的 SP 之间的距离
	// 可以优化,不适用 distance,改用 dot,_PowerUpFadeOutDistance 阈值重新调整一下即可
	// float dis = distance(i.srcXY_NewXY.xy, i.srcXY_NewXY.zw);
	float dis = dot(i.srcXY_NewXY.xy, i.srcXY_NewXY.zw);

	// jave.lin : 根据距离值做 fade out
	float fade = smoothstep(_PowerUpFadeOutDistance, 0, dis);
	// return half4(fade.xxx, 1);

	// jave.lin : 计算 alpha 的噪点抖动 的 3d noise 采用坐标的偏移值
	// float3 moveVec_alpha = _Time.y * _PowerUpSpeed_Alpha;
	// float3 moveVec = _Time.y * _PowerUpSpeed;

	// // jave.lin : 使用 3d noise 来采样,有点费,可以改用 2d noise,但只能使用 世界坐标xz,
	// // 或是 屏幕坐标 来作为采样坐标,会导致多个角色的气焰叠加一起时,抖动完全一致的问题,但可以使用 noise offset 来处理,这里偷懒就不这么挣了
	// float noise_alpha = snoise((i.newPosWS + moveVec_alpha) * _PowerUpNoiseScale_Alpha);
	// // float noise_alpha1 = snoise((i.newPosWS + moveVec_alpha * noise_alpha) * _PowerUpNoiseScale_Alpha);
	// // noise_alpha = max(noise_alpha, noise_alpha1);

	// // jave.lin : 将 距离的淡出值应用上
	// noise_alpha *= fade;

	// // jave.lin : noise alpha 再次和 rim 反向之去一个最大值,为的是:尽量让靠经原始坐标的像素的 alpha 值大一些
	// noise_alpha = max(noise_alpha, rimInv * fade);
	float noise_alpha = rimInv * fade;
	// float noise = snoise((i.newPosWS + moveVec + _PowerUpNoiseOffset) * _PowerUpNoiseScale);
	// return noise_alpha;
	
	// Unity_Remap_float4(noise, half2(0, 1), half2(0.5, 1), noise);
	
	// jave.lin : 使用 rim 反向值,来 混合两个颜色,color1 是气焰内色, color2 是气焰外色
	// half3 tintCol = lerp(_PowerUpOutlineColor1.rgb, _PowerUpOutlineColor2.rgb, rimInv);
	// jave.lin : 使用 rampTex 来映射气焰内外的着色
	half4 tintCol = tex2D(_PowerUpOutlineColorRampTex, half2(rimInv, 0.5));
	half4 finalCol = half4(/*noise * */tintCol.rgb, noise_alpha * tintCol.a);

	// jave.lin : 夹一下,避免溢出
	finalCol = saturate(finalCol);

	return finalCol;
}
//=================== power up END ===============================

Project

backup : 备份用,个人学习,不公开


关于效果落地

另外,这上面的代码都是 shader 层的实现
真正要落地,如果直接使用 3 个 pass 的来实现的化,会涉及到 半透明渲染顺序问题
这个在 BRP 项目,我们可以使用 CommandBuffer 来 hook 到对应的 after opaque rendering 之后渲染即可
URP 项目就更方便一些,直接扩展一个 RenderFeature 来专门渲染气焰效果即可,而且还可以 SetPassCall 合批,性能比 BRP 的高多

下面是在 BRP 中,落地方案实现(CommandBuffer)
在这里插入图片描述
请添加图片描述

比如,下面的绘制顺序的问题:
在这里插入图片描述

写个排序即可解决
在这里插入图片描述

但此种方法有个缺点,每个 character 模型 和 power up 气焰 绘制都需要独立的 stencil,那么绘制状态又变化,基本上就不能合批了

  • 9
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
Unity Shader是一种用于在Unity引擎中创建和控制图形渲染效果的编程语言。通过使用Unity Shader,开发人员可以自定义游戏中各种物体的外观和行为,从而实现更加逼真和出色的视觉效果。 而热图(Heatmap)是一种用于显示某个区域内物体热度分布的视觉化工具。在游戏开发中,热图通常用于统计和分析玩家在游戏中的行为和偏好,以便开发人员可以根据这些数据进行游戏优化和改进。 为了创建一个热图效果,我们可以使用Unity Shader来实现。首先,我们需要将游戏中各个物体按照玩家与其的互动情况和频率进行区分,不同的行为和频率可以对应不同的颜色或者纹理。接着,我们可以在Shader中根据这些信息来着色和渲染物体,以展示物体的热度分布。 在Shader中,我们可以通过为物体添加一张热图纹理,并使用该纹理来表示物体的热度值。热图纹理可以是一张灰度图,不同的灰度值对应不同的热度。然后,我们可以使用纹理坐标和采样操作来获取每个像素对应的热度值,并根据这些值来着色和渲染物体。 除了使用纹理来表示热度分布,我们还可以使用其他的技术和效果来增强热图的可视化效果。例如,我们可以使用颜色渐变和透明度来形成平滑的过渡效果,以更好地显示物体的热度变化。我们还可以添加动画效果,使热图效果更加生动和有趣。 总结而言,Unity Shader可以用于创建热图效果,通过着色和渲染来展示物体的热度分布。这样的热图可以帮助开发人员分析游戏中玩家的行为和偏好,从而优化和改进游戏的设计和内容。这些热图效果能够增强游戏的可视化效果,并提供有价值的数据供开发人员参考。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值