From 6579d30f558908c23889f9e691a5932a49ecdedd Mon Sep 17 00:00:00 2001 From: yum Date: Sat, 29 Mar 2025 20:23:19 -0700 Subject: Optimize tessellation code - vectorizing and frustrum culling --- 2ner.shader | 1 + cnlohr.cginc | 2 +- shatter_wave.cginc | 39 ++++++++++++++++++++++++--------------- tessellation.cginc | 42 ++++++++++++++++++++++++++++++++++++++---- 4 files changed, 64 insertions(+), 20 deletions(-) diff --git a/2ner.shader b/2ner.shader index a03b3d7..00a485f 100644 --- a/2ner.shader +++ b/2ner.shader @@ -549,6 +549,7 @@ Shader "yum_food/2ner" [HideInInspector] m_end_Tessellation_Heightmap("Heightmap", Float) = 0 [HideInInspector] m_start_Tessellation_Range_Factor("Range-based factor", Float) = 0 [ThryToggle(_TESSELLATION_RANGE_FACTOR)] _Tessellation_Range_Factor_Enabled("Enable", Float) = 0 + [Helpbox]_Tessellation_Range_Factor_Help("All distances are given in squared meters. For example, to set the far distance to 4 meters, enter 16.", Int) = 0 _Tessellation_Range_Factor_Distance_Near("Distance (near)", Float) = 1 _Tessellation_Range_Factor_Factor_Near("Factor (near)", Float) = 1 _Tessellation_Range_Factor_Distance_Far("Distance (far)", Float) = 1 diff --git a/cnlohr.cginc b/cnlohr.cginc index 52aca35..ce3e883 100644 --- a/cnlohr.cginc +++ b/cnlohr.cginc @@ -44,7 +44,7 @@ bool isMirror() { return _VRChatMirrorMode != 0; } // https://github.com/cnlohr/shadertrixx?tab=readme-ov-file#eye-center-position float3 getCenterCamPos() { #if defined(USING_STEREO_MATRICES) || defined(UNITY_SINGLE_PASS_STEREO) - return (unity_StereoWorldSpaceCameraPos[0] + unity_StereoWorldSpaceCameraPos[1]) / 2; + return (unity_StereoWorldSpaceCameraPos[0] + unity_StereoWorldSpaceCameraPos[1]) * 0.5; #else return isMirror() ? _VRChatMirrorCameraPos : _WorldSpaceCameraPos.xyz; #endif diff --git a/shatter_wave.cginc b/shatter_wave.cginc index 8636df6..f86a6ac 100644 --- a/shatter_wave.cginc +++ b/shatter_wave.cginc @@ -15,11 +15,22 @@ void shatterWaveVert(inout float3 objPos, float3 objNormal, float3 objTangent) { float3 wave_axis2 = normalize(_Shatter_Wave_Direction2); float3 wave_axis3 = normalize(_Shatter_Wave_Direction3); float4x3 wave_axes = float4x3(wave_axis0, wave_axis1, wave_axis2, wave_axis3); - float4x3 objPos_proj; - objPos_proj[0] = dot(objPos, wave_axis0) * normalize(wave_axis0); - objPos_proj[1] = dot(objPos, wave_axis1) * normalize(wave_axis1); - objPos_proj[2] = dot(objPos, wave_axis2) * normalize(wave_axis2); - objPos_proj[3] = dot(objPos, wave_axis3) * normalize(wave_axis3); + + float4 projDots = mul(wave_axes, objPos); + // Equivalent code: + // float4 projDots = float4( + // dot(objPos, wave_axis0), + // dot(objPos, wave_axis1), + // dot(objPos, wave_axis2), + // dot(objPos, wave_axis3) + // ); + + float4x3 objPos_proj = float4x3( + projDots.x * wave_axis0, + projDots.y * wave_axis1, + projDots.z * wave_axis2, + projDots.w * wave_axis3 + ); #if defined(_SHATTER_WAVE_AUDIOLINK) float4 wave_t; @@ -64,25 +75,23 @@ void shatterWaveVert(inout float3 objPos, float3 objNormal, float3 objTangent) { float4 wave_center = wave_t; - // TODO calculate signed distance from wave center - float4 distance_signed; - for (uint i = 0; i < 4; i++) { - float3 dist_to_center = objPos_proj[i] - wave_center[i] * wave_axes[i]; - distance_signed[i] = dot(dist_to_center, wave_axes[i]); - } + float4 distance_signed = float4( + dot(objPos_proj[0] - wave_center.x * wave_axes[0], wave_axes[0]), + dot(objPos_proj[1] - wave_center.y * wave_axes[1], wave_axes[1]), + dot(objPos_proj[2] - wave_center.z * wave_axes[2], wave_axes[2]), + dot(objPos_proj[3] - wave_center.w * wave_axes[3], wave_axes[3]) + ); #if defined(_SHATTER_WAVE_ROTATION) float4 thetas = clamp(distance_signed * _Shatter_Wave_Rotation_Strength, -1, 1) * TAU; + [unroll] for (uint i = 0; i < 4; i++) { objPos = rotate_vector(objPos, get_quaternion(wave_axes[i], thetas[i])); } #endif float4 offset = exp(-abs(distance_signed) * _Shatter_Wave_Power) * _Shatter_Wave_Amplitude; - objPos += objNormal * offset[0]; - objPos += objNormal * offset[1]; - objPos += objNormal * offset[2]; - objPos += objNormal * offset[3]; + objPos += objNormal * (offset.x + offset.y + offset.z + offset.w); } void shatterWaveFrag(inout float3 normal, float3 objPos) { diff --git a/tessellation.cginc b/tessellation.cginc index 2725776..30c3c63 100644 --- a/tessellation.cginc +++ b/tessellation.cginc @@ -16,20 +16,54 @@ tess_factors patch_constant(InputPatch patch) { tess_factors f; #if defined(_TESSELLATION) #if defined(_TESSELLATION_RANGE_FACTOR) - float d = length(getCenterCamPos() - patch[0].worldPos.xyz); + float3 vec = getCenterCamPos() - patch[0].worldPos.xyz; + float d2 = dot(vec, vec); float factor = lerp( _Tessellation_Range_Factor_Factor_Near, _Tessellation_Range_Factor_Factor_Far, smoothstep( _Tessellation_Range_Factor_Distance_Near, _Tessellation_Range_Factor_Distance_Far, - d)); + d2)); #else float factor = _Tessellation_Factor; #endif + { + // Frustum culling - don't tessellate if the patch is outside the viewport + // (xy) or behind the camera (w). We approximate this by checking the + // un-transformed and maximally transformed locations. Technically we could + // miss an intersection in the middle, but I haven't noticed any visible + // popping with this approach. + float max_displacement = _Tessellation_Heightmap_Scale * 0.5 + _Tessellation_Heightmap_Offset; + float3 p0d = patch[0].objPos.xyz + patch[0].normal.xyz * max_displacement; + float3 p1d = patch[1].objPos.xyz + patch[1].normal.xyz * max_displacement; + float3 p2d = patch[2].objPos.xyz + patch[2].normal.xyz * max_displacement; + float4 p0_clipPos = UnityObjectToClipPos(float4(patch[0].objPos.xyz, 1)); + float4 p1_clipPos = UnityObjectToClipPos(float4(patch[1].objPos.xyz, 1)); + float4 p2_clipPos = UnityObjectToClipPos(float4(patch[2].objPos.xyz, 1)); + float4 p0d_clipPos = UnityObjectToClipPos(float4(p0d, 1)); + float4 p1d_clipPos = UnityObjectToClipPos(float4(p1d, 1)); + float4 p2d_clipPos = UnityObjectToClipPos(float4(p2d, 1)); + float3 p0_ndc = p0_clipPos.xyz / p0_clipPos.w; + float3 p1_ndc = p1_clipPos.xyz / p1_clipPos.w; + float3 p2_ndc = p2_clipPos.xyz / p2_clipPos.w; + float3 p0d_ndc = p0d_clipPos.xyz / p0d_clipPos.w; + float3 p1d_ndc = p1d_clipPos.xyz / p1d_clipPos.w; + float3 p2d_ndc = p2d_clipPos.xyz / p2d_clipPos.w; + + bool on_screen = + (p0_ndc.x > -1 && p0_ndc.x < 1 && p0_ndc.y > -1 && p0_ndc.y < 1 && p0_clipPos.w > 0) || + (p1_ndc.x > -1 && p1_ndc.x < 1 && p1_ndc.y > -1 && p1_ndc.y < 1 && p1_clipPos.w > 0) || + (p2_ndc.x > -1 && p2_ndc.x < 1 && p2_ndc.y > -1 && p2_ndc.y < 1 && p2_clipPos.w > 0) || + (p0d_ndc.x > -1 && p0d_ndc.x < 1 && p0d_ndc.y > -1 && p0d_ndc.y < 1 && p0d_clipPos.w > 0) || + (p1d_ndc.x > -1 && p1d_ndc.x < 1 && p1d_ndc.y > -1 && p1d_ndc.y < 1 && p1d_clipPos.w > 0) || + (p2d_ndc.x > -1 && p2d_ndc.x < 1 && p2d_ndc.y > -1 && p2d_ndc.y < 1 && p2d_clipPos.w > 0); + factor = lerp(0, factor, on_screen); + } #else float factor = 1; #endif + f.edge[0] = factor; f.edge[1] = factor; f.edge[2] = factor; @@ -40,7 +74,7 @@ tess_factors patch_constant(InputPatch patch) { [UNITY_domain("tri")] [UNITY_outputcontrolpoints(3)] [UNITY_outputtopology("triangle_cw")] -[UNITY_partitioning("fractional_odd")] +[UNITY_partitioning("integer")] [UNITY_patchconstantfunc("patch_constant")] v2f hull( InputPatch patch, @@ -55,7 +89,7 @@ v2f domain( OutputPatch patch, float3 baryc : SV_DomainLocation) { - v2f o; + v2f o = (v2f) 0; #define DOMAIN_INTERP(fieldName) \ patch[0].fieldName * baryc.x + \ patch[1].fieldName * baryc.y + \ -- cgit v1.2.3