Lower world-anchored procedural fields
This commit is contained in:
parent
76126c27ca
commit
8feeefe6a5
7 changed files with 228 additions and 18 deletions
|
|
@ -11,6 +11,7 @@ namespace GameCult.Eve.UnityScene.Fields
|
|||
public const string SplatCountPropertyName = "_EveFieldsSplatCount";
|
||||
public const string ViewportToClipPropertyName = "_EveFieldsViewportToClip";
|
||||
public const string ChannelFilterPropertyName = "_EveFieldsChannelFilter";
|
||||
public const string SimulationTimePropertyName = "_EveFieldsSimulationTimeSeconds";
|
||||
|
||||
private const int SplatStrideBytes = 96;
|
||||
private EveFieldsGpuSplat[] splats = Array.Empty<EveFieldsGpuSplat>();
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ namespace GameCult.Eve.UnityScene.Fields
|
|||
private static readonly int SplatCountId = Shader.PropertyToID(EveFieldsSplatBuffer.SplatCountPropertyName);
|
||||
private static readonly int ViewportToClipId = Shader.PropertyToID(EveFieldsSplatBuffer.ViewportToClipPropertyName);
|
||||
private static readonly int ChannelFilterId = Shader.PropertyToID(EveFieldsSplatBuffer.ChannelFilterPropertyName);
|
||||
private static readonly int SimulationTimeId = Shader.PropertyToID(EveFieldsSplatBuffer.SimulationTimePropertyName);
|
||||
|
||||
public RenderTexture TargetTexture => targetTexture != null ? targetTexture : ownedTargetTexture;
|
||||
public int LastDrawnCount { get; private set; }
|
||||
|
|
@ -48,6 +49,7 @@ namespace GameCult.Eve.UnityScene.Fields
|
|||
commandBuffer.SetGlobalBuffer(SplatBufferId, splatBuffer.Buffer);
|
||||
commandBuffer.SetGlobalInt(SplatCountId, splatBuffer.Count);
|
||||
commandBuffer.SetGlobalInt(ChannelFilterId, channelFilter);
|
||||
commandBuffer.SetGlobalFloat(SimulationTimeId, (float)document.SimulationTimeSeconds);
|
||||
commandBuffer.SetGlobalMatrix(ViewportToClipId, BuildViewportToClip(document.Viewport));
|
||||
commandBuffer.DrawProcedural(Matrix4x4.identity, material, Mathf.Max(0, materialPass), MeshTopology.Triangles, 6, splatBuffer.Count);
|
||||
Graphics.ExecuteCommandBuffer(commandBuffer);
|
||||
|
|
|
|||
|
|
@ -15,31 +15,105 @@ StructuredBuffer<EveFieldsSplat> _EveFieldsSplats;
|
|||
float4x4 _EveFieldsViewportToClip;
|
||||
int _EveFieldsSplatCount;
|
||||
int _EveFieldsChannelFilter;
|
||||
float _EveFieldsSimulationTimeSeconds;
|
||||
float _ValueScale;
|
||||
|
||||
float hash21(float2 p)
|
||||
// Ashima Arts webgl-noise, translated by Keijiro Takahashi.
|
||||
// Copyright (C) 2011 Ashima Arts. Distributed under the MIT License.
|
||||
float3 EveFieldsMod289(float3 x)
|
||||
{
|
||||
p = frac(p * float2(123.34, 456.21));
|
||||
p += dot(p, p + 45.32);
|
||||
return frac(p.x * p.y);
|
||||
return x - floor(x / 289.0) * 289.0;
|
||||
}
|
||||
|
||||
float valueNoise(float2 p)
|
||||
float4 EveFieldsMod289(float4 x)
|
||||
{
|
||||
float2 i = floor(p);
|
||||
float2 f = frac(p);
|
||||
float a = hash21(i);
|
||||
float b = hash21(i + float2(1, 0));
|
||||
float c = hash21(i + float2(0, 1));
|
||||
float d = hash21(i + float2(1, 1));
|
||||
float2 u = f * f * (3 - 2 * f);
|
||||
return lerp(lerp(a, b, u.x), lerp(c, d, u.x), u.y) * 2 - 1;
|
||||
return x - floor(x / 289.0) * 289.0;
|
||||
}
|
||||
|
||||
float4 EveFieldsPermute(float4 x)
|
||||
{
|
||||
return EveFieldsMod289((x * 34.0 + 1.0) * x);
|
||||
}
|
||||
|
||||
float4 EveFieldsTaylorInvSqrt(float4 r)
|
||||
{
|
||||
return 1.79284291400159 - r * 0.85373472095314;
|
||||
}
|
||||
|
||||
float EveFieldsSimplexNoise3D(float3 v)
|
||||
{
|
||||
const float2 C = float2(1.0 / 6.0, 1.0 / 3.0);
|
||||
float3 i = floor(v + dot(v, C.yyy));
|
||||
float3 x0 = v - i + dot(i, C.xxx);
|
||||
float3 g = step(x0.yzx, x0.xyz);
|
||||
float3 l = 1.0 - g;
|
||||
float3 i1 = min(g.xyz, l.zxy);
|
||||
float3 i2 = max(g.xyz, l.zxy);
|
||||
float3 x1 = x0 - i1 + C.xxx;
|
||||
float3 x2 = x0 - i2 + C.yyy;
|
||||
float3 x3 = x0 - 0.5;
|
||||
i = EveFieldsMod289(i);
|
||||
float4 p = EveFieldsPermute(EveFieldsPermute(EveFieldsPermute(
|
||||
i.z + float4(0.0, i1.z, i2.z, 1.0))
|
||||
+ i.y + float4(0.0, i1.y, i2.y, 1.0))
|
||||
+ i.x + float4(0.0, i1.x, i2.x, 1.0));
|
||||
float4 j = p - 49.0 * floor(p / 49.0);
|
||||
float4 x_ = floor(j / 7.0);
|
||||
float4 y_ = floor(j - 7.0 * x_);
|
||||
float4 x = (x_ * 2.0 + 0.5) / 7.0 - 1.0;
|
||||
float4 y = (y_ * 2.0 + 0.5) / 7.0 - 1.0;
|
||||
float4 h = 1.0 - abs(x) - abs(y);
|
||||
float4 b0 = float4(x.xy, y.xy);
|
||||
float4 b1 = float4(x.zw, y.zw);
|
||||
float4 s0 = floor(b0) * 2.0 + 1.0;
|
||||
float4 s1 = floor(b1) * 2.0 + 1.0;
|
||||
float4 sh = -step(h, 0.0);
|
||||
float4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
|
||||
float4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
|
||||
float3 g0 = float3(a0.xy, h.x);
|
||||
float3 g1 = float3(a0.zw, h.y);
|
||||
float3 g2 = float3(a1.xy, h.z);
|
||||
float3 g3 = float3(a1.zw, h.w);
|
||||
float4 norm = EveFieldsTaylorInvSqrt(float4(
|
||||
dot(g0, g0), dot(g1, g1), dot(g2, g2), dot(g3, g3)));
|
||||
g0 *= norm.x;
|
||||
g1 *= norm.y;
|
||||
g2 *= norm.z;
|
||||
g3 *= norm.w;
|
||||
float4 m = max(0.6 - float4(
|
||||
dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
|
||||
m *= m;
|
||||
m *= m;
|
||||
return 42.0 * dot(m, float4(
|
||||
dot(x0, g0), dot(x1, g1), dot(x2, g2), dot(x3, g3)));
|
||||
}
|
||||
|
||||
float EveFieldsCellDistanceB(float2 p)
|
||||
{
|
||||
p = frac(p) - 0.5;
|
||||
return (length(p) * 1.5 + 0.25) *
|
||||
max(abs(p.x) * 0.866 + p.y * 0.5, -p.y);
|
||||
}
|
||||
|
||||
float EveFieldsAnimatedCellNoiseB(float2 p, float time)
|
||||
{
|
||||
float2 o = sin(float2(1.93, 0.0) + time) * 0.166;
|
||||
float a = EveFieldsCellDistanceB(p + float2(o.x, 0));
|
||||
float b = EveFieldsCellDistanceB(p + float2(0, 0.5 + o.y));
|
||||
p = mul(p + 0.5, -float2x2(0.5, -0.866, 0.866, 0.5));
|
||||
float c = EveFieldsCellDistanceB(p + float2(o.x, 0));
|
||||
float d = EveFieldsCellDistanceB(p + float2(0, 0.5 + o.y));
|
||||
p = mul(p + 0.5, -float2x2(0.5, -0.866, 0.866, 0.5));
|
||||
float e = EveFieldsCellDistanceB(p + float2(o.x, 0));
|
||||
float f = EveFieldsCellDistanceB(p + float2(0, 0.5 + o.y));
|
||||
return min(min(min(a, b), min(c, d)), min(e, f)) * 2.0;
|
||||
}
|
||||
|
||||
struct Varyings
|
||||
{
|
||||
float4 position : SV_POSITION;
|
||||
float2 localUv : TEXCOORD0;
|
||||
float2 fieldWorld : TEXCOORD2;
|
||||
nointerpolation uint instanceId : TEXCOORD1;
|
||||
};
|
||||
|
||||
|
|
@ -55,6 +129,7 @@ Varyings Vert(uint vertexId : SV_VertexID, uint instanceId : SV_InstanceID)
|
|||
Varyings output;
|
||||
output.position = mul(_EveFieldsViewportToClip, float4(world, 0, 1));
|
||||
output.localUv = local;
|
||||
output.fieldWorld = world;
|
||||
output.instanceId = instanceId;
|
||||
return output;
|
||||
}
|
||||
|
|
@ -88,10 +163,20 @@ float4 Frag(Varyings input) : SV_Target
|
|||
float source = 1;
|
||||
if (sourceKind == 1 || sourceKind == 2)
|
||||
{
|
||||
float timeOffset = sourceKind == 2 ? _Time.y * splat.layerSource.z : 0;
|
||||
source = valueNoise(input.localUv * splat.sourceFrequencyPhase.xy + splat.sourceFrequencyPhase.zw + timeOffset);
|
||||
if (splat.layerSource.w != 0) source = abs(source);
|
||||
float timeCoordinate = sourceKind == 2
|
||||
? _EveFieldsSimulationTimeSeconds * splat.layerSource.z
|
||||
: 0;
|
||||
source = EveFieldsSimplexNoise3D(float3(
|
||||
input.fieldWorld * splat.sourceFrequencyPhase.xy + splat.sourceFrequencyPhase.zw,
|
||||
timeCoordinate));
|
||||
}
|
||||
else if (sourceKind == 3)
|
||||
{
|
||||
source = EveFieldsAnimatedCellNoiseB(
|
||||
input.fieldWorld * splat.sourceFrequencyPhase.xy + splat.sourceFrequencyPhase.zw,
|
||||
_EveFieldsSimulationTimeSeconds * splat.layerSource.z);
|
||||
}
|
||||
if (((int)round(splat.layerSource.w) & 1) != 0) source = abs(source);
|
||||
return splat.value * (alpha * source * _ValueScale);
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -174,6 +174,94 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
UnityEngine.Object.DestroyImmediate(host);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RasterizerAnchorsAnimatedSimplexToFieldWorldAndPublishedSimulationTime()
|
||||
{
|
||||
var first = ProceduralDocument(
|
||||
0, 100, 50, EveFieldsSplatSourceKinds.AnimatedSimplexNoise, 2);
|
||||
var shifted = ProceduralDocument(
|
||||
25, 125, 75, EveFieldsSplatSourceKinds.AnimatedSimplexNoise, 2);
|
||||
var later = ProceduralDocument(
|
||||
0, 100, 50, EveFieldsSplatSourceKinds.AnimatedSimplexNoise, 7);
|
||||
|
||||
var firstValue = RenderFieldPixel(first, 64, 64);
|
||||
var shiftedValue = RenderFieldPixel(shifted, 32, 64);
|
||||
var laterValue = RenderFieldPixel(later, 64, 64);
|
||||
|
||||
Assert.That(shiftedValue, Is.EqualTo(firstValue).Within(0.015f),
|
||||
"the same field-world coordinate must not swim when the viewport moves");
|
||||
Assert.That(Mathf.Abs(laterValue - firstValue), Is.GreaterThan(0.02f),
|
||||
"animation must use the document's simulation time rather than Unity wall time");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RasterizerLowersPortableAnimatedCellNoiseB()
|
||||
{
|
||||
var first = ProceduralDocument(
|
||||
0, 100, 50, EveFieldsSplatSourceKinds.AnimatedCellNoiseB, 2);
|
||||
var later = ProceduralDocument(
|
||||
0, 100, 50, EveFieldsSplatSourceKinds.AnimatedCellNoiseB, 11);
|
||||
|
||||
var firstValue = RenderFieldPixel(first, 57, 71);
|
||||
var laterValue = RenderFieldPixel(later, 57, 71);
|
||||
|
||||
Assert.That(firstValue, Is.GreaterThanOrEqualTo(0));
|
||||
Assert.That(Mathf.Abs(laterValue - firstValue), Is.GreaterThan(0.005f));
|
||||
}
|
||||
|
||||
private static EveFieldsSplatsDocument ProceduralDocument(
|
||||
double min,
|
||||
double max,
|
||||
double center,
|
||||
int sourceKind,
|
||||
double simulationTime)
|
||||
{
|
||||
return new EveFieldsSplatsDocument
|
||||
{
|
||||
SimulationTimeSeconds = simulationTime,
|
||||
Viewport = new EveFieldsViewport { MinX = min, MinY = 0, MaxX = max, MaxY = 100 },
|
||||
Splats = new EveFieldsSplatSoa
|
||||
{
|
||||
Count = 1,
|
||||
CenterX = new[] { center },
|
||||
CenterY = new[] { 50d },
|
||||
HalfExtentX = new[] { 50d },
|
||||
HalfExtentY = new[] { 50d },
|
||||
RotationCos = new[] { 1d },
|
||||
RotationSin = new[] { 0d },
|
||||
Channel = new[] { 0 },
|
||||
Falloff = new[] { EveFieldsSplatFalloffs.Solid },
|
||||
ValueR = new[] { 1d },
|
||||
ValueA = new[] { 1d },
|
||||
LayerIndex = new[] { 0 },
|
||||
SourceKind = new[] { sourceKind },
|
||||
FrequencyX = new[] { 0.037d },
|
||||
FrequencyY = new[] { 0.041d },
|
||||
PhaseX = new[] { 0.17d },
|
||||
PhaseY = new[] { -0.23d },
|
||||
AnimationSpeed = new[] { 0.31d },
|
||||
SourceFlags = new[] { (double)EveFieldsSplatSourceFlags.AbsoluteValue }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static float RenderFieldPixel(EveFieldsSplatsDocument document, int x, int y)
|
||||
{
|
||||
var host = new GameObject("eve-fields-procedural-capture");
|
||||
var rasterizer = host.AddComponent<EveFieldsSplatRasterizer>();
|
||||
var output = rasterizer.Render(document, 128, 128);
|
||||
var previous = RenderTexture.active;
|
||||
RenderTexture.active = output;
|
||||
var readback = new Texture2D(128, 128, TextureFormat.RGBAFloat, false, true);
|
||||
readback.ReadPixels(new Rect(0, 0, 128, 128), 0, 0);
|
||||
readback.Apply();
|
||||
RenderTexture.active = previous;
|
||||
var value = readback.GetPixel(x, y).r;
|
||||
UnityEngine.Object.DestroyImmediate(readback);
|
||||
UnityEngine.Object.DestroyImmediate(host);
|
||||
return value;
|
||||
}
|
||||
|
||||
private sealed class CaptureDocument : IEveFieldsSplatsDocument
|
||||
{
|
||||
public string Schema => EveFieldsSchemas.Splats;
|
||||
|
|
|
|||
27
packages/org.gamecult.eve.unity-scene/Third Party Notices.md
Normal file
27
packages/org.gamecult.eve.unity-scene/Third Party Notices.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Third-party notices
|
||||
|
||||
## Ashima Arts webgl-noise
|
||||
|
||||
The portable 3D simplex implementation in `Runtime/Fields/EveFieldsSplatsCore.hlsl`
|
||||
is derived from Ashima Arts' `webgl-noise`, translated to Unity shader syntax by
|
||||
Keijiro Takahashi.
|
||||
|
||||
Copyright (C) 2011 Ashima Arts.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5702faba00994033870c858bb2124d91
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "org.gamecult.eve.unity-scene",
|
||||
"displayName": "Eve Unity Scene Lowering",
|
||||
"version": "0.3.42",
|
||||
"version": "0.3.43",
|
||||
"unity": "2021.3",
|
||||
"description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.",
|
||||
"dependencies": {
|
||||
"org.gamecult.cultlib": "1.0.13",
|
||||
"org.gamecult.eve.plugin-fields": "0.2.1",
|
||||
"org.gamecult.eve.plugin-fields": "0.2.2",
|
||||
"org.gamecult.eve.surface": "0.2.2",
|
||||
"com.unity.mathematics": "1.3.2",
|
||||
"com.unity.modules.particlesystem": "1.0.0"
|
||||
|
|
|
|||
Reference in a new issue