-
-
Save TSUMIKISEISAKU/5123d8c583770c94be2d7c6d7d34e37f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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