制作交互式雪地着色器的URP版本,需要两个文件,一个用于镶嵌部分的 .hlsl 文件和一个 .shader 文件,复制代码并新建文件。
SnowTessellation.hlsl 文件
//#ifndef TESSELLATION_CGINC_INCLUDED
//#define TESSELLATION_CGINC_INCLUDED
#if defined(SHADER_API_D3D11) || defined(SHADER_API_GLES3) || defined(SHADER_API_GLCORE) || defined(SHADER_API_VULKAN) || defined(SHADER_API_METAL) || defined(SHADER_API_PSSL)
#define UNITY_CAN_COMPILE_TESSELLATION 1
# define UNITY_domain domain
# define UNITY_partitioning partitioning
# define UNITY_outputtopology outputtopology
# define UNITY_patchconstantfunc patchconstantfunc
# define UNITY_outputcontrolpoints outputcontrolpoints
#endif
struct Varyings
{
float3 worldPos : TEXCOORD1;
float3 normal : NORMAL;
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float3 viewDir : TEXCOORD3;
float fogFactor : TEXCOORD4;
};
float _Tess;
float _MaxTessDistance;
float _Fade;
struct TessellationFactors
{
float edge[3] : SV_TessFactor;
float inside : SV_InsideTessFactor;
};
struct Attributes
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct ControlPoint
{
float4 vertex : INTERNALTESSPOS;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
[UNITY_domain("tri")]
[UNITY_outputcontrolpoints(3)]
[UNITY_outputtopology("triangle_cw")]
[UNITY_partitioning("fractional_odd")]
[UNITY_patchconstantfunc("patchConstantFunction")]
ControlPoint hull(InputPatch<ControlPoint, 3> patch, uint id : SV_OutputControlPointID)
{
return patch[id];
}
TessellationFactors UnityCalcTriEdgeTessFactors (float3 triVertexFactors)
{
TessellationFactors tess;
tess.edge[0] = 0.5 * (triVertexFactors.y + triVertexFactors.z);
tess.edge[1] = 0.5 * (triVertexFactors.x + triVertexFactors.z);
tess.edge[2] = 0.5 * (triVertexFactors.x + triVertexFactors.y);
tess.inside = (triVertexFactors.x + triVertexFactors.y + triVertexFactors.z) / 3.0f;
return tess;
}
float CalcDistanceTessFactor(float4 vertex, float minDist, float maxDist, float tess)
{
float3 worldPosition = mul(unity_ObjectToWorld, vertex).xyz;
float dist = distance(worldPosition, _WorldSpaceCameraPos);
float f = clamp(1.0 - (dist - minDist) / (maxDist - minDist), 0.01, 1.0);
return f * tess;
}
TessellationFactors DistanceBasedTess(float4 v0, float4 v1, float4 v2, float minDist, float maxDist, float tess)
{
float3 f;
f.x = CalcDistanceTessFactor(v0, minDist, maxDist, tess);
f.y = CalcDistanceTessFactor(v1, minDist, maxDist, tess);
f.z = CalcDistanceTessFactor(v2, minDist, maxDist, tess);
return UnityCalcTriEdgeTessFactors(f);
}
uniform float3 _Position;
uniform sampler2D _GlobalEffectRT;
uniform float _OrthographicCamSize;
sampler2D _Noise;
float _NoiseScale, _SnowHeight, _NoiseWeight, _SnowDepth;
TessellationFactors patchConstantFunction(InputPatch<ControlPoint, 3> patch)
{
float minDist = 2.0;
float maxDist = _MaxTessDistance;
TessellationFactors f;
return DistanceBasedTess(patch[0].vertex, patch[1].vertex, patch[2].vertex, minDist, maxDist, _Tess);
}
float4 GetShadowPositionHClip(Attributes input)
{
float3 positionWS = TransformObjectToWorld(input.vertex.xyz);
float3 normalWS = TransformObjectToWorldNormal(input.normal);
float4 positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, 0));
#if UNITY_REVERSED_Z
positionCS.z = min(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE);
#else
positionCS.z = max(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE);
#endif
return positionCS;
}
Varyings vert(Attributes input)
{
Varyings output;
float3 worldPosition = mul(unity_ObjectToWorld, input.vertex).xyz;
//create local uv
float2 uv = worldPosition.xz - _Position.xz;
uv = uv / (_OrthographicCamSize * 2);
uv += 0.5;
// Effects RenderTexture Reading
float4 RTEffect = tex2Dlod(_GlobalEffectRT, float4(uv, 0, 0));
// smoothstep to prevent bleeding
RTEffect *= smoothstep(0.99, _Fade, uv.x) * smoothstep(0.99, _Fade,1- uv.x);
RTEffect *= smoothstep(0.99, _Fade, uv.y) * smoothstep(0.99, _Fade,1- uv.y);
// worldspace noise texture
float SnowNoise = tex2Dlod(_Noise, float4(worldPosition.xz * _NoiseScale, 0, 0)).r;
output.viewDir = SafeNormalize(GetCameraPositionWS() - worldPosition);
// move vertices up where snow is
input.vertex.xyz += SafeNormalize(input.normal) * saturate(( _SnowHeight) + (SnowNoise * _NoiseWeight)) * saturate(1-(RTEffect.g * _SnowDepth));
// transform to clip space
#ifdef SHADERPASS_SHADOWCASTER
output.vertex = GetShadowPositionHClip(input);
#else
output.vertex = TransformObjectToHClip(input.vertex.xyz);
#endif
//outputs
output.worldPos = mul(unity_ObjectToWorld, input.vertex).xyz;
output.normal = input.normal;
output.uv = input.uv;
output.fogFactor = ComputeFogFactor(output.vertex.z);
return output;
}
[UNITY_domain("tri")]
Varyings domain(TessellationFactors factors, OutputPatch<ControlPoint, 3> patch, float3 barycentricCoordinates : SV_DomainLocation)
{
Attributes v;
#define Interpolate(fieldName) v.fieldName = \
patch[0].fieldName * barycentricCoordinates.x + \
patch[1].fieldName * barycentricCoordinates.y + \
patch[2].fieldName * barycentricCoordinates.z;
Interpolate(vertex)
Interpolate(uv)
Interpolate(normal)
return vert(v);
}
InteractiveSnowURP.shader 文件
Shader "Custom/Snow Interactive" {
Properties{
[Header(Main)]
_Noise("Snow Noise", 2D) = "gray" {}
_NoiseScale("Noise Scale", Range(0,2)) = 0.1
_NoiseWeight("Noise Weight", Range(0,2)) = 0.1
[HDR]_ShadowColor("Shadow Color", Color) = (0.5,0.5,0.5,1)
[Space]
[Header(Tesselation)]
_MaxTessDistance("Max Tessellation Distance", Range(10,100)) = 50
_Tess("Tessellation", Range(1,32)) = 20
[Space]
[Header(Snow)]
[HDR]_Color("Snow Color", Color) = (0.5,0.5,0.5,1)
[HDR]_PathColorIn("Snow Path Color In", Color) = (0.5,0.5,0.7,1)
[HDR]_PathColorOut("Snow Path Color Out", Color) = (0.5,0.5,0.7,1)
_PathBlending("Snow Path Blending", Range(0,3)) = 0.3
_MainTex("Snow Texture", 2D) = "white" {}
_SnowHeight("Snow Height", Range(0,2)) = 0.3
_SnowDepth("Snow Path Depth", Range(-2,2)) = 0.3
_SnowTextureOpacity("Snow Texture Opacity", Range(0,2)) = 0.3
_SnowTextureScale("Snow Texture Scale", Range(0,2)) = 0.3
[Space]
[Header(Sparkles)]
_SparkleScale("Sparkle Scale", Range(0,10)) = 10
_SparkCutoff("Sparkle Cutoff", Range(0,10)) = 0.8
_SparkleNoise("Sparkle Noise", 2D) = "gray" {}
[Space]
[Header(Rim)]
_RimPower("Rim Power", Range(0,20)) = 20
[HDR]_RimColor("Rim Color Snow", Color) = (0.5,0.5,0.5,1)
}
HLSLINCLUDE
// Includes
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"
#include "SnowTessellation.hlsl"
#pragma vertex TessellationVertexProgram
#pragma hull hull
#pragma domain domain
// Keywords
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN
#pragma multi_compile _ _ADDITIONAL_LIGHT_SHADOWS
#pragma multi_compile _ _SHADOWS_SOFT
#pragma multi_compile_fog
ControlPoint TessellationVertexProgram(Attributes v)
{
ControlPoint p;
p.vertex = v.vertex;
p.uv = v.uv;
p.normal = v.normal;
return p;
}
ENDHLSL
SubShader{
Tags{ "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline"}
Pass{
Tags { "LightMode" = "UniversalForward" }
HLSLPROGRAM
// vertex happens in snowtessellation.hlsl
#pragma fragment frag
#pragma target 4.0
sampler2D _MainTex, _SparkleNoise;
float4 _Color, _RimColor;
float _RimPower;
float4 _PathColorIn, _PathColorOut;
float _PathBlending;
float _SparkleScale, _SparkCutoff;
float _SnowTextureOpacity, _SnowTextureScale;
float4 _ShadowColor;
half4 frag(Varyings IN) : SV_Target{
// Effects RenderTexture Reading
float3 worldPosition = mul(unity_ObjectToWorld, IN.vertex).xyz;
float2 uv = IN.worldPos.xz - _Position.xz;
uv /= (_OrthographicCamSize * 2);
uv += 0.5;
// effects texture
float4 effect = tex2D(_GlobalEffectRT, uv);
// mask to prevent bleeding
effect *= smoothstep(0.99, 0.9, uv.x) * smoothstep(0.99, 0.9,1- uv.x);
effect *= smoothstep(0.99, 0.9, uv.y) * smoothstep(0.99, 0.9,1- uv.y);
// worldspace Noise texture
float3 topdownNoise = tex2D(_Noise, IN.worldPos.xz * _NoiseScale).rgb;
// worldspace Snow texture
float3 snowtexture = tex2D(_MainTex, IN.worldPos.xz * _SnowTextureScale).rgb;
//lerp between snow color and snow texture
float3 snowTex = lerp(_Color.rgb,snowtexture * _Color.rgb, _SnowTextureOpacity);
//lerp the colors using the RT effect path
float3 path = lerp(_PathColorOut.rgb * effect.g, _PathColorIn.rgb, saturate(effect.g * _PathBlending));
float3 mainColors = lerp(snowTex,path, saturate(effect.g));
// lighting and shadow information
float shadow = 0;
half4 shadowCoord = TransformWorldToShadowCoord(IN.worldPos);
#if _MAIN_LIGHT_SHADOWS_CASCADE || _MAIN_LIGHT_SHADOWS
Light mainLight = GetMainLight(shadowCoord);
shadow = mainLight.shadowAttenuation;
#else
Light mainLight = GetMainLight();
#endif
// extra point lights support
float3 extraLights;
int pixelLightCount = GetAdditionalLightsCount();
for (int j = 0; j < pixelLightCount; ++j) {
Light light = GetAdditionalLight(j, IN.worldPos, half4(1, 1, 1, 1));
float3 attenuatedLightColor = light.color * (light.distanceAttenuation * light.shadowAttenuation);
extraLights += attenuatedLightColor;
}
float4 litMainColors = float4(mainColors,1) ;
extraLights *= litMainColors.rgb;
// add in the sparkles
float sparklesStatic = tex2D(_SparkleNoise, IN.worldPos.xz * _SparkleScale).r;
float cutoffSparkles = step(_SparkCutoff,sparklesStatic);
litMainColors += cutoffSparkles *saturate(1- (effect.g * 2)) * 4;
// add rim light
half rim = 1.0 - dot((IN.viewDir), IN.normal) * topdownNoise;
litMainColors += _RimColor * pow(abs(rim), _RimPower);
// ambient and mainlight colors added
half4 extraColors;
extraColors.rgb = litMainColors.rgb * mainLight.color.rgb * (shadow + unity_AmbientSky.rgb);
extraColors.a = 1;
// colored shadows
float3 coloredShadows = (shadow + (_ShadowColor.rgb * (1-shadow)));
litMainColors.rgb = litMainColors.rgb * mainLight.color * (coloredShadows);
// everything together
float4 final = litMainColors+ extraColors + float4(extraLights,0);
// add in fog
final.rgb = MixFog(final.rgb, IN.fogFactor);
return final;
}
ENDHLSL
}
// casting shadows is a little glitchy, I've turned it off, but maybe in future urp versions it works better?
// Shadow Casting Pass
// Pass
// {
// Name "ShadowCaster"
// Tags { "LightMode" = "ShadowCaster" }
// ZWrite On
// ZTest LEqual
// Cull Off
// HLSLPROGRAM
// #pragma target 3.0
// // Support all the various light ypes and shadow paths
// #pragma multi_compile_shadowcaster
// // Register our functions
// #pragma fragment frag
// // A custom keyword to modify logic during the shadow caster pass
// half4 frag(Varyings IN) : SV_Target{
// return 0;
// }
// ENDHLSL
// }
}
}
设置:
- 需要一个向下的正交相机,尺寸 15-ish。
- 将下面的代码添加到它以将位置和渲染纹理提供给着色器
- 创建一个新的 RenderTexture 并将其拖到 RT 插槽中,将 Player Transform 添加到 Interactor 插槽。
- 为正交相机创建一个 新图层 以查看粒子所在的位置,确保主摄像头看不到这一层。
- 添加 SnowTessellation.hlsl 和 InteractiveSnowHLSL.shader 文件,并将它们放在同一个文件夹中。
- 创建新材料并将其分配给想要下雪的平面。
- 将距离生成粒子附加到新层上的玩家。
将代码添加到它以将位置和渲染纹理提供给着色器
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SetInteractiveShaderEffects : MonoBehaviour
{
[SerializeField]
RenderTexture rt;
[SerializeField]
Transform target;
// Start is called before the first frame update
void Awake()
{
Shader.SetGlobalTexture("_GlobalEffectRT", rt);
Shader.SetGlobalFloat("_OrthographicCamSize", GetComponent<Camera>().orthographicSize);
}
private void Update()
{
transform.position = new Vector3(target.transform.position.x, transform.position.y, target.transform.position.z);
Shader.SetGlobalVector("_Position", transform.position);
}
}
Tessellation:

距离细分:

Tessellation 是基于距离的,只有靠近相机才会有更高的多边形。最大距离可以用_MaxTessDistance控制
雪道:

这是通过播放器上方的 RenderTexture 和 正交摄影机 完成的。玩家将附着在远处产生的粒子,确保这些粒子位于只有正交相机才能看到的新层上。
粒子:

作为交互网格的子对象,使用 Start Speed 0 设置新的unity粒子系统。Start Lifetime是想下雪多长时间,把它设置在10 到 15 之间。将 Start Size 设置为与使雪变形的网格一样宽,有轻微的随机变化
在 发射 中,粒子需要产生 10 Over Distance (超远距离),并确保始终有缩进,还将 Rate over Time (随时间变化的速率) 设置为 1 和 2 之间。形状只是设置为微小的 0.1 边缘。

将 Color over Lifetime 设置为以绿色渐变淡化为 alpha 0(确保它是纯绿色以避免与其他粒子效果混合)
最后在渲染器中,将渲染模式设置为水平广告牌。
将 材质 设置为标准的白色圆圈,使用 Legacy Shaders/Particles/Additive (Soft) 着色器(或等效的软添加模式),这里使用的是简单的软圆。

它现在应该看起来与此类似,就像一条水滴状的小径。大小的变化使它看起来更自然
降雪:

快速设置一些下雪效果:
- 常规:起始大小在 0.5 和 0.7 之间,将最大粒子增加到 2000-ish
- 发射:时间速率 200
- 形状:盒子,从体积中发射,x-50 y-50 的比例
- 生命周期内的速度:世界空间,0.1 和 -0.1 之间的轨道 X/z
- 渲染器:Billboard 标准粒子着色器
- 可选择开启碰撞
设置示例:
