Blend SrcAlpha OneMinusSrcAlpha // Traditional transparency
Blend One OneMinusSrcAlpha // Premultiplied transparency
Blend One One // Additive
Blend OneMinusDstColor One // Soft Additive
Blend DstColor Zero // Multiplicative
Blend DstColor SrcColor // 2x Multiplicative
SimpleEmitter.compute
#pragma kernel CSMain
float dt;
float time;
float pi;
uint maxParticles = 1024;
float maxAge;
struct Particle
{
int index;
float3 position;
float3 velocity;
float size;
float age;
float normAge;
int type;
};
RWStructuredBuffer <Particle> particles;
[numthreads( 1, 1, 1 )]
void CSMain ( uint3 Gid : SV_GroupID, uint3 DTid : SV_DispatchThreadID, uint3 GTid : SV_GroupThreadID, uint GI : SV_GroupIndex )
{
uint index = DTid.x;
if (index < maxParticles)
{
Particle p = particles[index];
p.position.y = p.index; //just check if the index is correct by giving a Y position
particles[index] = p;
}
}
SimpleEmitter.cs
using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;
public class SimpleEmitter : MonoBehaviour
{
struct Particle
{
public int index;
public Vector3 position;
public Vector3 velocity;
public float size;
public float age;
public float normAge;
public int type;
}
public ComputeShader computeShader;
public Material material;
public int maxParticles = 1000000;
public float maxAge = 3.0f;
public float particleSize = 0.5f;
private ComputeBuffer particles;
private int particleSizeOf;
void Start ()
{
Particle p = new Particle();
particleSizeOf = Marshal.SizeOf(p);
Particle[] pInitBuffer = new Particle[maxParticles];
for (int i = 0; i < maxParticles; i++)
{
p = new Particle();
p.index = i;
p.type = 0;
p.age = 0;
p.normAge = 0.1f;
p.size = particleSize * 0.5f + Random.value * particleSize;
p.velocity = new Vector3(0, 0, 0);
pInitBuffer[i] = p;
}
particles = new ComputeBuffer(maxParticles, particleSizeOf, ComputeBufferType.Default);
particles.SetData(pInitBuffer);
computeShader.SetBuffer(0, "particles", particles);
}
void Update()
{
computeShader.SetFloat("dt", Time.deltaTime);
computeShader.SetFloat("time", Time.time);
computeShader.SetFloat("pi", Mathf.PI);
computeShader.SetInt("maxParticles", maxParticles);
computeShader.SetFloat("maxAge", maxAge);
computeShader.Dispatch(0, 64, 1, 1);
}
public void OnPostRender()
{
material.SetPass(0);
material.SetFloat("maxAge", maxAge);
material.SetBuffer("particles", particles);
Graphics.DrawProcedural(MeshTopology.Triangles, maxParticles, 0);
}
void OnDisable()
{
particles.Release();
}
}
Geometry – Pixel – Fragment – Shader – Simple Emitter
Shader "Custom/SimpleRS"
{
Properties
{
_ParticleTexture ("Diffuse Tex", 2D) = "white" {}
_Ramp1Texture ("G_Ramp1", 2D) = "white" {}
}
SubShader
{
Pass
{
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
Blend OneMinusDstColor One
Cull Off
Lighting Off
ZWrite Off
Fog { Color (0,0,0,0) }
CGPROGRAM
#pragma target 5.0
#pragma vertex VSMAIN
#pragma fragment PSMAIN
#pragma geometry GSMAIN
#include "UnityCG.cginc"
struct Particle
{
int index;
float3 position;
float3 velocity;
float size;
float age;
float normAge;
int type;
};
StructuredBuffer<Particle> particles;
Texture2D _ParticleTexture;
SamplerState sampler_ParticleTexture;
Texture2D _Ramp1Texture;
SamplerState sampler_Ramp1Texture;
float maxAge;
float maxRad;
struct VS_INPUT
{
uint vertexid : SV_VertexID;
};
//--------------------------------------------------------------------------------
struct GS_INPUT
{
float4 position : SV_POSITION;
float size : TEXCOORD0;
float age : TEXCOORD1;
float type : TEXCOORD2;
};
//--------------------------------------------------------------------------------
struct PS_INPUT
{
float4 position : SV_POSITION;
float2 texcoords : TEXCOORD0;
float size : TEXCOORD1;
float age : TEXCOORD2;
float type : TEXCOORD3;
};
//--------------------------------------------------------------------------------
GS_INPUT VSMAIN( in VS_INPUT input )
{
GS_INPUT output;
output.position.xyz = particles[input.vertexid].position;
output.position.w = 1.0;
output.age = particles[input.vertexid].normAge;
output.size = particles[input.vertexid].size;
output.type = particles[input.vertexid].type;
return output;
}
//--------------------------------------------------------------------------------
[maxvertexcount(4)]
void GSMAIN( point GS_INPUT p[1], inout TriangleStream<PS_INPUT> triStream )
{
float4 pos = mul(UNITY_MATRIX_MVP, p[0].position);
float halfS = p[0].size * 0.5f;
float4 offset = mul(UNITY_MATRIX_P, float4(halfS, halfS, 0, 1));
float4 v[4];
v[0] = pos + float4(offset.x, offset.y, 0, 1);
v[1] = pos + float4(offset.x, -offset.y, 0, 1);
v[2] = pos + float4(-offset.x, offset.y, 0, 1);
v[3] = pos + float4(-offset.x, -offset.y, 0, 1);
PS_INPUT pIn;
pIn.position = v[0];
pIn.texcoords = float2(1.0f, 0.0f);
pIn.size = p[0].size;
pIn.age = p[0].age;
pIn.type = p[0].type;
triStream.Append(pIn);
pIn.position = v[1];
pIn.texcoords = float2(1.0f, 1.0f);
triStream.Append(pIn);
pIn.position = v[2];
pIn.texcoords = float2(0.0f, 0.0f);
triStream.Append(pIn);
pIn.position = v[3];
pIn.texcoords = float2(0.0f, 1.0f);
triStream.Append(pIn);
}
//--------------------------------------------------------------------------------
float4 PSMAIN( in PS_INPUT input ) : COLOR
{
float4 color = _ParticleTexture.Sample( sampler_ParticleTexture, input.texcoords );
float4 tint = _Ramp1Texture.Sample(sampler_Ramp1Texture, float2(min(1.0, input.age),0));
color *= tint;
if (input.age == 0) discard;
return color;
}
//--------------------------------------------------------------------------------
ENDCG
}
}
}
Particle – Pixel Version:
Particle.Shader
Shader "particle"
{
// Bound with the inspector.
Properties
{
_Color ("Main Color", Color) = (0, 1, 1,0.3)
_SpeedColor ("Speed Color", Color) = (1, 0, 0, 0.3)
_colorSwitch ("Switch", Range (0, 120)) = 60
}
SubShader
{
Pass
{
Blend SrcAlpha one
CGPROGRAM
#pragma target 5.0
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
// The same particle data structure used by both the compute shader and the shader.
struct Particle
{
float3 position;
float3 velocity;
};
// structure linking the data between the vertex and the fragment shader
struct FragInput
{
float4 color : COLOR;
float4 position : SV_POSITION;
};
// The buffer holding the particles shared with the compute shader.
StructuredBuffer<Particle> particleBuffer;
// Variables from the properties.
float4 _Color;
float4 _SpeedColor;
float _colorSwitch;
// DX11 vertex shader these 2 parameters come from the draw call: "1" and "particleCount",
// SV_VertexID: "1" is the number of vertex to draw peer particle, we could easily make quad or sphere particles with this.
// SV_InstanceID: "particleCount", number of particles...
FragInput vert (uint id : SV_VertexID, uint inst : SV_InstanceID)
{
FragInput fragInput;
// color computation
float speed = length(particleBuffer[inst].velocity);
float lerpValue = clamp(speed / _colorSwitch, 0, 1);
fragInput.color = lerp(_Color, _SpeedColor, lerpValue);
// position computation
fragInput.position = mul (UNITY_MATRIX_MVP, float4(particleBuffer[inst].position, 1));
return fragInput;
}
// this just pass through the color computed in the vertex program
float4 frag (FragInput fragInput) : COLOR
{
return fragInput.color;
}
ENDCG
}
}
Fallback Off
}
MoveParticle.Compute
#pragma kernel main
// The same particle data structure used by both the compute shader and the shader.
struct Particle
{
float3 position;
float3 velocity;
};
// The buffer holding the particles shared with the regular shader.
RWStructuredBuffer<Particle> particleBuffer;
// parameters from GPU
float deltaTime; // Even here deltaTime is needed!
float3 base; // Base position.
float3 orbit; // Orbit position.
float targetStrengh; // Strengh, from the inspector!
float orbit_weighting;
[numthreads(32,1,1)] // 32 is the minimal size to fullfill the wrap. this is just the number of thread to run by wrap, "x, y, z" make easy 3D indexing.
void main (uint3 id : SV_DispatchThreadID)
{
// particleBuffer[id.x] is the particle this thread must Update, according to the thread ID.
// Direction and distance to target.
float3 basedir1 = normalize((base - particleBuffer[id.x].position));
float3 orbitdir2 = normalize((orbit - particleBuffer[id.x].position));
float basedist1 = distance(base, particleBuffer[id.x].position);
float orbitdist2 = distance(orbit, particleBuffer[id.x].position);
float3 dir;
float dist;
float mult;
if(basedist1 < orbitdist2)
{
dist = basedist1;
dir = basedir1;
mult = orbit_weighting*2;
}
else
{
dist = orbitdist2;
dir = orbitdir2;
mult = (1 - orbit_weighting)*2;
}
if(dist > 5)
{
particleBuffer[id.x].velocity += mult * targetStrengh * dir * deltaTime / dist;
}
else
{
particleBuffer[id.x].velocity -= mult * targetStrengh * dir * deltaTime / dist;
}
particleBuffer[id.x].velocity = particleBuffer[id.x].velocity* 0.98f;
particleBuffer[id.x].position += particleBuffer[id.x].velocity * deltaTime;
}
TestParticle.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
public class TestParticle : MonoBehaviour
{
// The same particle data structure used by both the compute shader and the shader.
struct Particle
{
public Vector3 position;
public Vector3 velocity;
};
public static List<TestParticle> list; // Static list, just to call Render() from the camera.
public int warpCount = 10; // The number particle /32.
public float initialSpread = 0.1f; // Initial speed to the center of the sphere.
public float pPowerMag = 100; // velocity change from the mouse position.
public float orbit_weighting = 0.5f;
public Material material; // Use "particle" shader.
public ComputeShader computeShader; // Use "moveParticle" compute shader.
const int warpSize = 32; // GPUs process data by warp, 32 for every modern ones.
int particleCount; // = warpSize * warpCount.
ComputeBuffer particleBuffer; // The GPU buffer holding the particules.
public GameObject frisbee1;
public GameObject orbitingSphere;
public UI_Control ui_Control;
void Start ()
{
// Just init the static list
if (list == null)
list = new List<TestParticle>();
list.Add(this);
particleCount = warpCount * warpSize;
// Init particles
Particle[] particleArray = new Particle[particleCount];
for (int i = 0; i < particleCount; ++i)
{
particleArray[i].position = Random.insideUnitSphere * initialSpread;
particleArray[i].velocity = particleArray[i].position.normalized;
}
// Instanciate and initialise the GPU buffer.
particleBuffer = new ComputeBuffer(particleCount, 24); // 24 = sizeof(Particle)
particleBuffer.SetData(particleArray);
// bind the buffer to both the compute shader and the shader.
computeShader.SetBuffer(0, "particleBuffer", particleBuffer);
material.SetBuffer ("particleBuffer", particleBuffer);
}
void Update ()
{
// Get the mouse position in the 3D space (a flat box collider catch the ray) .
float[] targ1 = {0f, 0f, 0f };
float[] targ2 = { 0f, 0f, 0f };
Vector3 pos = frisbee1.transform.position;
targ1[0] = pos.x;//BASE
targ1[1] = pos.y;
targ1[2] = pos.z;
pos = orbitingSphere.transform.position;
targ2[0] = pos.x;//ORBIT
targ2[1] = pos.y;
targ2[2] = pos.z;
pPowerMag = ui_Control.pParticlePower;
orbit_weighting = ui_Control.orbit_weighting;
// Initialise the compute shader variables.
computeShader.SetFloat("targetStrengh", pPowerMag);
computeShader.SetFloat("deltaTime", Time.deltaTime);
computeShader.SetFloats("base", targ1);
computeShader.SetFloats("orbit", targ2);
computeShader.SetFloats("orbit_weighting", orbit_weighting);
// Start the compute shader (move every particle for this frame).
computeShader.Dispatch(0, warpCount, 1, 1);
}
// Called by the camera in OnRender
public void Render ()
{
// Bind the pass to the pipeline then call a draw (this use the buffer bound in Start() instead of a VBO).
material.SetPass (0);
Graphics.DrawProcedural (MeshTopology.Points, 1, particleCount);
}
void OnDestroy()
{
list.Remove(this);
// Unity cry if the GPU buffer isn't manually cleaned
particleBuffer.Release();
}
}
BillboardGeomShader.shader
Shader "Charles Will Code It/Geom/Billboard"
{
Properties
{
_Sprite("Sprite", 2D) = "white" {}
_Color("Color", Color) = (1,1,1,1)
_Size("Size", Vector) = (1,1,0,0)
_Wind("Wind", Vector) = (0,0,0,0)
}
SubShader
{
Tags{ "Queue" = "Overlay+100" "RenderType" = "Transparent" }
LOD 100
Blend SrcAlpha OneMinusSrcAlpha
Cull off
ZWrite off
Pass
{
CGPROGRAM
#pragma target 5.0
#pragma vertex vert
#pragma geometry geom
#pragma fragment frag
#pragma multi_compile_fog
#include "UnityCG.cginc"
sampler2D _Sprite;
float4 _Color = float4(1,0.5f,0.0f,1);
float2 _Size = float2(1,1);
float3 _worldPos;
float3 _Wind = float3(0, 0, 0);
int _StaticCylinderSpherical = 0; // 0 = static 1 = cylinder 2 = spherical
struct data {
float3 pos;
};
//The buffer containing the points we want to draw.
StructuredBuffer<data> buf_Points;
struct input
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
};
input vert(uint id : SV_VertexID)
{
input o;
o.pos = float4(buf_Points[id].pos + _worldPos, 1.0f);
return o;
}
float4 RotPoint(float4 p, float3 offset, float3 sideVector, float3 upVector)
{
float3 finalPos = p.xyz;
finalPos += offset.x * sideVector;
finalPos += offset.y * upVector;
return float4(finalPos,1);
}
[maxvertexcount(4)]
void geom(point input p[1], inout TriangleStream<input> triStream)
{
float2 halfS = _Size;
float4 v[4];
if (_StaticCylinderSpherical == 0)
{
v[0] = p[0].pos.xyzw + float4(-halfS.x, -halfS.y, 0, 0);
v[1] = p[0].pos.xyzw + float4(-halfS.x, halfS.y, 0, 0);
v[2] = p[0].pos.xyzw + float4(halfS.x, -halfS.y, 0, 0);
v[3] = p[0].pos.xyzw + float4(halfS.x, halfS.y, 0, 0);
}
else
{
float3 up = normalize(float3(0, 1, 0) + (_Wind * -.5));
float3 look = _WorldSpaceCameraPos - p[0].pos.xyz;
if (_StaticCylinderSpherical == 1)
look.y = 0;
look = normalize(look);
float3 right = normalize(cross(look, up));
up = normalize(cross(right, look));
v[0] = RotPoint(p[0].pos , float3(-halfS.x,-halfS.y,0), right,up);
v[1] = RotPoint(p[0].pos , float3(-halfS.x,halfS.y,0), right,up);
v[2] = RotPoint(p[0].pos , float3(halfS.x,-halfS.y,0), right,up);
v[3] = RotPoint(p[0].pos , float3(halfS.x,halfS.y,0), right,up);
}
input pIn;
pIn.pos = mul(UNITY_MATRIX_VP, v[0]);
pIn.uv = float2(0.0f, 0.0f);
UNITY_TRANSFER_FOG(pIn, pIn.pos);
triStream.Append(pIn);
pIn.pos = mul(UNITY_MATRIX_VP, v[1]);
pIn.uv = float2(0.0f, 1.0f);
UNITY_TRANSFER_FOG(pIn, pIn.pos);
triStream.Append(pIn);
pIn.pos = mul(UNITY_MATRIX_VP, v[2]);
pIn.uv = float2(1.0f, 0.0f);
UNITY_TRANSFER_FOG(pIn, pIn.pos);
triStream.Append(pIn);
pIn.pos = mul(UNITY_MATRIX_VP, v[3]);
pIn.uv = float2(1.0f, 1.0f);
UNITY_TRANSFER_FOG(pIn, pIn.pos);
triStream.Append(pIn);
}
float4 frag(input i) : COLOR
{
fixed4 col = tex2D(_Sprite, i.uv) * _Color;
UNITY_APPLY_FOG(i.fogCoord, col); // apply fog
return col;
}
ENDCG
}
}
Fallback Off
}
BillboardGeomScript.cs
using UnityEngine;
using System.Collections;
public class BillboardGeomScript : MonoBehaviour
{
public Shader geomShader;
Material material;
public Texture2D sprite;
public Vector2 size = Vector2.one;
public Color color = new Color(1.0f, 0.6f, 0.3f, 0.03f);
ComputeBuffer outputBuffer;
ComputeShaderOutput cso;
WeatherShaderOutput wso;
public Vector3 Wind;
[Range(0,2)]
[Tooltip("Billboard type 0 = static, 1 = Cylindrical, 2 = Sphecrical")]
public int billboardType = 2;
data[] points;
public bool UseComputeData = false;
struct data
{
public Vector3 pos;
};
// Use this for initialization
void Start()
{
material = new Material(geomShader);
points = new data[]
{
new data() { pos = Vector3.left + Vector3.up },
new data() { pos = Vector3.right + Vector3.up },
new data() { pos = Vector3.zero},
new data() { pos = Vector3.left + Vector3.down },
new data() { pos = Vector3.right + Vector3.down}
};
//points = new data[] { new data() { pos = Vector3.zero } };
if (!UseComputeData)
{
outputBuffer = new ComputeBuffer(points.Length, 12);
outputBuffer.SetData(points);
}
}
void OnRenderObject()
{
if (UseComputeData)
{
cso = GetComponent<ComputeShaderOutput>();
wso = GetComponent<WeatherShaderOutput>();
if (outputBuffer == null && cso != null)
outputBuffer = cso.outputBuffer;
if (outputBuffer == null && wso != null)
outputBuffer = wso.outputBuffer;
if (cso != null)
cso.Dispatch();
else
{
wso.Dispatch();
Wind = wso.wind;
}
}
material.SetPass(0);
material.SetColor("_Color", color);
material.SetBuffer("buf_Points", outputBuffer);
material.SetTexture("_Sprite", sprite);
material.SetVector("_Size", size);
material.SetInt("_StaticCylinderSpherical", billboardType);
material.SetVector("_worldPos", transform.position);
material.SetVector("_Wind", Wind);
Graphics.DrawProcedural(MeshTopology.Points, outputBuffer.count);
}
void OnDestroy()
{
outputBuffer.Release();
}
}
WeatherShaderOutput.cs
using UnityEngine;
using System.Collections;
[AddComponentMenu("Scripts/Charles Will Code It/Compute Shader Scripts/WeatherShaderOutput")]
public class WeatherShaderOutput : MonoBehaviour
{
#region Compute Shader Fields and Properties
/// <summary>
/// The Compute shader we will use
/// </summary>
public ComputeShader computeShader;
/// <summary>
/// The total number of verticies to calculate. We are going to have 32 * 32 work groups, each processing 16 * 16 * 1 points.
/// </summary>
public const int VertCount = 32 * 32 * 16 * 16 * 1;
/// <summary>
/// The initial start position for each of the points to be calculated.
/// </summary>
ComputeBuffer startPointBuffer;
/// <summary>
/// This buffer will store the calculated data resulting from the Compute shader.
/// </summary>
public ComputeBuffer outputBuffer;
/// <summary>
/// This buffer is used to hold constant values for each point, in this case a value for time.
/// </summary>
ComputeBuffer constantBuffer;
ComputeBuffer modBuffer;
public Shader PointShader;
Material PointMaterial;
[Tooltip("Speed weather is falling at, 1 would be slow like snow, 10, rain")]
[Range(1,200)]
public float Speed = 1;
[Tooltip("Does the weather element wobble, like snow?")]
public bool Wobble = false;
[Tooltip("Wind Direction")]
public Vector3 wind = Vector3.zero;
[Tooltip("Distance between weather elements")]
public float spacing = 5;
/// <summary>
/// A Reference to the CS Kernel we want to use.
/// </summary>
int CSKernel;
#endregion
void InitializeBuffers()
{
// Set start point compute buffer
startPointBuffer = new ComputeBuffer(VertCount, 4); // create space in the buffer for 3 float per point (float = 4 bytes)
// Set const compute buffer size
constantBuffer = new ComputeBuffer(1, 4);
modBuffer = new ComputeBuffer(VertCount, 8);
// Set output buffer size.
outputBuffer = new ComputeBuffer(VertCount, 12);
// These values will be the starting y coords for each point.
float[] values = new float[VertCount];
Vector2[] mods = new Vector2[VertCount];
for (int i = 0; i < VertCount; i++)
{
values[i] = Random.value * 2 * Mathf.PI;
mods[i] = new Vector2(.1f + Random.value, .1f + Random.value);
}
modBuffer.SetData(mods);
// Load starting valuse into the compute buffer
startPointBuffer.SetData(values);
// Only need to set the offsets once in the CS
computeShader.SetBuffer(CSKernel, "startPointBuffer", startPointBuffer);
}
public void Dispatch()
{
constantBuffer.SetData(new[] { Time.time * .01f });
computeShader.SetBuffer(CSKernel, "modBuffer", modBuffer);
computeShader.SetBuffer(CSKernel, "constBuffer", constantBuffer);
computeShader.SetBuffer(CSKernel, "outputBuffer", outputBuffer);
computeShader.SetFloat("Speed", Speed);
computeShader.SetInt("wobble", Wobble ? 1 : 0);
computeShader.SetVector("wind", wind);
computeShader.SetFloat("spacing", spacing);
computeShader.Dispatch(CSKernel, 32, 32, 1);
}
void ReleaseBuffers()
{
modBuffer.Release();
constantBuffer.Release();
startPointBuffer.Release();
outputBuffer.Release();
}
void Start()
{
CSKernel = computeShader.FindKernel("CSMain");
PointMaterial = new Material(PointShader);
InitializeBuffers();
}
void OnPostRender()
{
if (!SystemInfo.supportsComputeShaders)
{
Debug.LogWarning("Compute shaders not supported (not using DX11?)");
return;
}
Dispatch();
PointMaterial.SetPass(0);
PointMaterial.SetBuffer("buf_Points", outputBuffer);
Graphics.DrawProcedural(MeshTopology.Points, VertCount);
}
private void OnDisable()
{
ReleaseBuffers();
}
}
WeatherGenerator.Compute
#pragma kernel CSMain
// Thread group size
#define thread_group_size_x 16
#define thread_group_size_y 16
#define thread_group_size_z 1
float Speed = 1;
int wobble = 0;
float3 wind = float3(0,0,0);
float spacing = 5;
// Structure to store x,y,z position data in. This is used to populate the outputBuffer
struct positionStruct
{
float3 pos;
};
// Structure to describe the data in the startPointBuffer
struct startYStruct
{
float offset;
};
// Structure to describe the data in the constBuffer
struct constStruct
{
float t;
};
struct posMod
{
float2 mod;
};
// Compute Buffers
RWStructuredBuffer<posMod> modBuffer;
RWStructuredBuffer<constStruct> constBuffer;
RWStructuredBuffer<startYStruct> startPointBuffer;
RWStructuredBuffer<positionStruct> outputBuffer;
[numthreads(thread_group_size_x, thread_group_size_y, thread_group_size_z)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
int idx = id.x + id.y * thread_group_size_x * 32;
float3 centre = float3(-thread_group_size_x * 15,0,-thread_group_size_y * 15);
float3 pos = (float3(id.x, id.z + (startPointBuffer[idx].offset ), id.y) + centre) * spacing;
pos.y = (lerp((spacing * 2) * 32, 0, (pos.y + (constBuffer[0].t * (modBuffer[idx].mod.x * Speed) )) % 1 )) * spacing;
if(wobble)
{
pos.x += (cos(constBuffer[0].t * (modBuffer[idx].mod.x * 100))) * spacing;
pos.z += (sin(constBuffer[0].t * (modBuffer[idx].mod.y * 100))) * spacing;
}
wind.y = 0; // Don't want the weather going up do we...
pos += (wind * constBuffer[0].t * 100);
outputBuffer[idx].pos = pos;
}