Skip to content

Instantly share code, notes, and snippets.

@TSUMIKISEISAKU
Created September 7, 2023 05:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TSUMIKISEISAKU/5123d8c583770c94be2d7c6d7d34e37f to your computer and use it in GitHub Desktop.
Save TSUMIKISEISAKU/5123d8c583770c94be2d7c6d7d34e37f to your computer and use it in GitHub Desktop.
// deform mesh like sculpting
#pragma kernel Sculpt
// size of thread group
#define SIMULATION_BLOCK_SIZE 256
// vertex buffer
RWByteAddressBuffer _vertexBufferWrite;
RWByteAddressBuffer _vertexBufferRead;
// index buffer
ByteAddressBuffer _indexBuffer;
// topology structure
struct Topology
{
int v1; int v2; int v3; int v4; int v5; int v6; int v7; int v8; int v9; int v10;
};
// topology buffer
RWStructuredBuffer<Topology> _topologyBuffer;
// vertex color buffer; each color value is normalized
RWStructuredBuffer<float4> _colorBuffer;
// vertex position buffer for relaxing mesh
groupshared bool _positionBuffer[SIMULATION_BLOCK_SIZE];
// input position for sculpting
float3 _sculptPos;
// input direction for sculpting
float3 _sculptDir;
// sculpting radius
float _sculptRadius;
// intensity of sculpting
float _intensity;
// render texture for uv offset
RWTexture2D<float4> _uvOffsetTex;
// randomized UV offset value
float2 _uvOffset;
// resolution of _ufOffsetTex
float _uvOffsetTexSize;
// vertex count
uint _vertexCount;
RWStructuredBuffer<float3> _displaceVertexBuffer;
// radius of the update area
#define RADIUS 0.05
// radius of paint area of _uvOffsetTex
#define PAINT_RADIUS 0.025
float3 LoadVertex(uint index)
{
uint pi = index * 11 * 4; // index * (3[position.xyz] + 3[normal.xyz] + 1[Color32(8bit * 4)] + 2[uv.xy] + 2[uv.xy]) * 4[bytes]
return asfloat(_vertexBufferWrite.Load3(pi));
}
float3 LoadVertex_Read(uint index)
{
uint pi = index * 11 * 4; // index * (3[position.xyz] + 3[normal.xyz] + 1[Color32(8bit * 4)] + 2[uv.xy] + 2[uv.xy]) * 4[bytes]
return asfloat(_vertexBufferRead.Load3(pi));
}
float4 LoadColor(uint index)
{
return _colorBuffer[index];
}
float3 LoadNormal(uint index)
{
uint pi = index * 11 * 4; // index * (3[position.xyz] + 3[normal.xyz] + 1[Color32(8bit * 4)] + 2[uv.xy] + 2[uv.xy]) * 4[bytes]
uint ni = pi + 3 * 4; // pi + 3[position.xyz] * 4[bytes]
return asfloat(_vertexBufferWrite.Load3(ni));
}
void StoreDisplacedVertex(uint index, float3 position)
{
uint pi = index * 11 * 4; // index * (3[position.xyz] + 3[normal.xyz] + 1[Color32(8bit * 4)] + 2[uv.xy] + 2[uv.xy]) * 4[bytes]
uint ni = pi + 3 * 4; // pi + 3[position.xyz] * 4[bytes]
_vertexBufferWrite.Store3(pi, asuint(position));
}
void StoreNormal(uint index, float3 normal)
{
uint pi = index * 11 * 4; // index * (3[position.xyz] + 3[normal.xyz] + 1[Color32(8bit * 4)] + 2[uv.xy] + 2[uv.xy]) * 4[bytes]
uint ni = pi + 3 * 4; // pi + 3[position.xyz] * 4[bytes]
_vertexBufferWrite.Store3(ni, asuint(normal));
}
void StoreDisplacedVertex_Read(uint index, float3 position /*, float3 normal*/)
{
uint pi = index * 11 * 4; // index * (3[position.xyz] + 3[normal.xyz] + 1[Color32(8bit * 4)] + 2[uv.xy] + 2[uv.xy]) * 4[bytes]
_vertexBufferRead.Store3(pi, asuint(position));
}
// access elements of Topology by the index
int GetTopologyIndex(Topology topology, int index)
{
if (index == 0) return topology.v1;
else if (index == 1) return topology.v2;
else if (index == 2) return topology.v3;
else if (index == 3) return topology.v4;
else if (index == 4) return topology.v5;
else if (index == 5) return topology.v6;
else if (index == 6) return topology.v7;
else if (index == 7) return topology.v8;
else if (index == 8) return topology.v9;
else if (index == 9) return topology.v10;
else return -1;
}
// displace vertex
float3 Displce(float3 position)
{
// NOTE: displace calculation is done in LOCAL SPACE!
// displace mesh
// direction to displace / x can be displaced both directions
float3 displaceDir = float3(0, -1, 1);
// position and distance seem object space
float dist = distance(position, _sculptPos);
if (dist > _sculptRadius) return position;
float angleWeight = dot(normalize(_sculptDir), normalize(position - _sculptPos));
angleWeight = max(saturate(angleWeight), 0.3);
float offset = _intensity * smoothstep(_sculptRadius, 0, dist) * angleWeight;
float3 dir = normalize(position - _sculptPos);
// constrain y-axis displace direction
dir.y = sign(dir.y) * displaceDir.y < 0 ? -dir.y : dir.y;
// constrain z-axis displace direction
dir.z = sign(dir.z) * displaceDir.z < 0 ? -dir.z : dir.z;
// disable to offset along x-axis
//dir.x = 0;
float3 newPos = position + dir * offset;
return newPos;
}
// relax mesh; weighted average
float3 RelaxMesh(uint index)
{
// iterate through the connected points
float3 thisPos = LoadVertex(index);
float3 midPos = float3(0, 0, 0);
int count = 0;
float dist = distance(thisPos, _sculptPos);
if (dist > _sculptRadius) return thisPos;
Topology topology = _topologyBuffer[index];
// sum of distance between the points
float distSum = 0;
for (uint j = 0; j < 10; j++)
{
int otherVertice = GetTopologyIndex(topology, j);
if (otherVertice == -1) continue;
float3 otherPos = LoadVertex(asuint(otherVertice));
distSum += distance(thisPos, otherPos);
}
// calculated relaxed position
float weightSum = 0;
for (uint i = 0; i < 10; i++)
{
int otherVertice = GetTopologyIndex(topology, i);
if (otherVertice == -1) continue;
float3 otherPos = LoadVertex(asuint(otherVertice));
float weight = 1.0 - distance(thisPos, otherPos) / distSum;
midPos += otherPos * weight;
weightSum += weight;
count++;
}
// avoid 0 division
midPos = count > 0 ? midPos / weightSum : thisPos;
// direction to displace / x can be displaced both directions
float3 displaceDir = float3(0, -1, 1);
// constrain y-axis displace direction
midPos.y = sign(midPos.y - thisPos.y) * displaceDir.y < 0 ? thisPos.y : midPos.y;
// constrain z-axis displace direction
midPos.z = sign(midPos.z - thisPos.z) * displaceDir.z < 0 ? thisPos.z : midPos.z;
return midPos;
}
float3 CalculateNormal(float3 p0, float3 p1, float3 p2)
{
float3 d10 = p1 - p0;
float3 d20 = p2 - p0;
return normalize(cross(d10, d20));
}
float2 LoadUV(uint index)
{
uint pi = index * 11 * 4; // index * (3[position.xyz] + 3[normal.xyz] + 1[Color32(8bit * 4)] + 2[uv.xy] + 2[uv.xy]) * 4[bytes]
uint ui = pi + 9 * 4; // pi + (3[position.xyz] + 3[normal.xyz] + 1[Color32(8bit * 4)] + 2[uv.xy]) * 4[bytes]
return asfloat(_vertexBufferWrite.Load2(ui));
}
// paint _uvOffsetTex based on the uv of the vertex
void PaintUVOffset(uint index)
{
// read the uv coordinates of the vertex
float2 uv = LoadUV(index);
// iterate through the pixels around the uv coordinates
int range = _uvOffsetTexSize * PAINT_RADIUS;
int2 centerPixel = int2(uv.x * _uvOffsetTexSize, uv.y * _uvOffsetTexSize);
for (int x = centerPixel.x - range; x < centerPixel.x + range; x++)
{
for (int y = centerPixel.y - range; y < centerPixel.y + range; y++)
{
// clip the iteration range within the actual texture size
if (x < 0 || y < 0 || x >= _uvOffsetTexSize || y >= _uvOffsetTexSize) continue;
// make the draw area circular
float pixelDist = length(float2(x - centerPixel.x, y - centerPixel.y));
if (pixelDist > range) continue;
// write updated offset value on the texture
// r:x, g:y, b:offset area
_uvOffsetTex[uint2(x, y)] = float4(_uvOffset, max(_uvOffset.x, _uvOffset.y), 1);
}
}
}
[numthreads(SIMULATION_BLOCK_SIZE, 1, 1)]
void Sculpt
(
uint3 id : SV_DispatchThreadID, // global thread index (= Gid.x * SIMULATION_BLOCK_SIZE + GTid.x)
uint GI : SV_GroupIndex
)
{
// calculate position
uint3 triIndex = _indexBuffer.Load3(id.x * 3 * 4); // id * 3[triangle vertices] * 4[bytes]
float3 p0 = LoadVertex(triIndex.x);
float3 p1 = LoadVertex(triIndex.y);
float3 p2 = LoadVertex(triIndex.z);
float4 c0 = LoadColor(triIndex.x);
float4 c1 = LoadColor(triIndex.y);
float4 c2 = LoadColor(triIndex.z);
float3 dp0 = Displce(p0);
float3 dp1 = Displce(p1);
float3 dp2 = Displce(p2);
// mask by vertex color
dp0 = lerp(p0, dp0, c0.x);
dp1 = lerp(p1, dp1, c1.x);
dp2 = lerp(p2, dp2, c2.x);
/** per-vertex mesh relaxing ***/
// store displaced positions temporarily
StoreDisplacedVertex(triIndex.x, dp0);
StoreDisplacedVertex(triIndex.y, dp1);
StoreDisplacedVertex(triIndex.z, dp2);
// store position for collider mesh
_displaceVertexBuffer[triIndex.x] = dp0;
_displaceVertexBuffer[triIndex.y] = dp1;
_displaceVertexBuffer[triIndex.z] = dp2;
float3 pp0 = LoadVertex(id.x);
// contain displaced position in the shared memory
// index in the buffer identifies the index of vertex
// since SIMULATION_BLOCK_SIZE > _positionBuffer.length, it sould work
_positionBuffer[GI] = false;
// wait for other threads reach at this block
GroupMemoryBarrierWithGroupSync();
// relax mesh
// to avoid multiple execution on a single vertice,
// SV_DispatchThreadID is used as a index of the vertex, not triangles in this section
// apply old relax function for the green vertex (corner)
float4 rc0 = LoadColor(id.x);
float3 rp0 = LoadVertex(id.x);
rp0 = id.x < _vertexCount ? RelaxMesh(id.x) : rp0;
_positionBuffer[GI] = true;
// wait for other threads reach at this block
GroupMemoryBarrierWithGroupSync();
if (id.x < _vertexCount)
{
// mask by vertex color
float4 cc0 = LoadColor(id.x);
rp0 = lerp(pp0, rp0, cc0.x);
// overwrite if the new position is in the range to update
bool isP0InUpdateRange = distance(pp0, _sculptPos) <= _sculptRadius;
if (isP0InUpdateRange)
{
// recalculate normal
float3 normal = LoadNormal(id.x);
StoreDisplacedVertex(id.x, rp0);
PaintUVOffset(id.x);
// store position for collider mesh
_displaceVertexBuffer[id.x] = rp0;
}
}
// wait for other threads reach at this block
GroupMemoryBarrierWithGroupSync();
/*** per-triangle normal recalculation ***/
float3 drp0 = LoadVertex(triIndex.x);
float3 drp1 = LoadVertex(triIndex.y);
float3 drp2 = LoadVertex(triIndex.z);
float3 normal = CalculateNormal(drp0, drp1, drp2);
// store normal
bool isP0InUpdateRange = distance(drp0, _sculptPos) <= _sculptRadius;
bool isP1InUpdateRange = distance(drp1, _sculptPos) <= _sculptRadius;
bool isP2InUpdateRange = distance(drp2, _sculptPos) <= _sculptRadius;
if (isP0InUpdateRange)
{
StoreNormal(triIndex.x, normal);
}
if (isP1InUpdateRange)
{
StoreNormal(triIndex.y, normal);
}
if (isP2InUpdateRange)
{
StoreNormal(triIndex.z, normal);
}
/*************************************/
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment