×

# iOS特效之你家玻璃碎了

### 原理

1. 给每个小方块创建一个四边形，并配置好UV，显示图片对应的部分。假设有n个小方块，如果使用三角形绘制，就需要6 * n个顶点。每个顶点有5个float，代表位置和uv。
2. 每个小方块使用一个顶点绘制，绘制时使用point绘制模式，将point_size设置成小方块大小，这样只需要n个顶点。本文采用的就是这种方式，这种方式唯一的缺点是小方块只能是正方形。
第二步就很简单了，只需要使用加速度即可。

### 顶点生成

``````private func buildPointData() -> [Float] {
var vertexDataArray: [Float] = []
let pointSize: Float = 12
let viewWidth: Float = Float(UIScreen.main.bounds.width)
let viewHeight: Float = Float(UIScreen.main.bounds.height)
let rowCount = Int(viewHeight / pointSize) + 1
let colCount = Int(viewWidth / pointSize) + 1
let sizeXInMetalTexcoord: Float = pointSize / viewWidth * 2.0
let sizeYInMetalTexcoord: Float = pointSize / viewHeight * 2.0
pointTransforms = [matrix_float4x4].init()
pointMoveInfo = [PointMoveInfo].init()
for row in 0..<rowCount {
for col in 0..<colCount {
let centerX = Float(col) * sizeXInMetalTexcoord + sizeXInMetalTexcoord / 2.0 - 1.0
let centerY = Float(row) * sizeYInMetalTexcoord + sizeYInMetalTexcoord / 2.0 - 1.0
vertexDataArray.append(centerX)
vertexDataArray.append(centerY)
vertexDataArray.append(0.0)
vertexDataArray.append(Float(col) / Float(colCount))
vertexDataArray.append(Float(row) / Float(rowCount))

pointTransforms.append(GLKMatrix4Identity.toFloat4x4())
pointMoveInfo.append(PointMoveInfo.defaultMoveInfo(centerX: centerX, centerY: centerY))
}
}

uniforms.pointTexcoordScaleX = sizeXInMetalTexcoord / 2.0
uniforms.pointTexcoordScaleY = sizeYInMetalTexcoord / 2.0
uniforms.pointSizeInPixel = pointSize
return vertexDataArray
}
``````

``````// 构建顶点
self.vertexArray = buildPointData()
let vertexBufferSize = MemoryLayout<Float>.size * self.vertexArray.count
self.vertexBuffer = device.makeBuffer(bytes: self.vertexArray, length: vertexBufferSize, options: MTLResourceOptions.cpuCacheModeWriteCombined)
``````

### 更新运动信息

``````struct PointMoveInfo {
var xSpeed: Float
var ySpeed: Float
var xAccelerate: Float
var yAccelerate: Float
var originCenterX: Float
var originCenterY: Float
var translateX: Float
var translateY: Float

...
}
``````

``````pointMoveInfo[i].ySpeed += Float(deltaTime) * pointMoveInfo[i].yAccelerate
if pointMoveInfo[i].ySpeed < maxYSpeed {
pointMoveInfo[i].ySpeed = maxYSpeed
}
``````

``````pointMoveInfo[i].translateX += Float(deltaTime) * pointMoveInfo[i].xSpeed
pointMoveInfo[i].translateY += Float(deltaTime) * pointMoveInfo[i].ySpeed
let newMatrix = GLKMatrix4MakeTranslation(pointMoveInfo[i].translateX, pointMoveInfo[i].translateY, 0)
pointTransforms[i] = newMatrix.toFloat4x4()
``````

``````let realY = pointMoveInfo[i].translateY + pointMoveInfo[i].originCenterY
let realX = pointMoveInfo[i].translateX + pointMoveInfo[i].originCenterX
if realY <= -1.0 {
pointMoveInfo[i].ySpeed = -pointMoveInfo[i].ySpeed * 0.6
if fabs(pointMoveInfo[i].ySpeed) < 0.01 {
pointMoveInfo[i].ySpeed = 0
}
}
if realX <= -1.0 || realX >= 1.0 {
pointMoveInfo[i].xSpeed = -pointMoveInfo[i].xSpeed * 0.6
if fabs(pointMoveInfo[i].xSpeed) < 0.01 {
pointMoveInfo[i].xSpeed = 0
}
}
``````

### 渲染

``````override func draw(renderEncoder: MTLRenderCommandEncoder) {
renderEncoder.setVertexBuffer(self.vertexBuffer, offset: 0, index: 0)
renderEncoder.setFragmentTexture(self.imageTexture, index: 0)

let uniformBuffer = device.makeBuffer(bytes: self.uniforms.data(), length: Uniforms.sizeInBytes(), options:
MTLResourceOptions.cpuCacheModeWriteCombined)
renderEncoder.setVertexBuffer(uniformBuffer, offset: 0, index: 1)
renderEncoder.setFragmentBuffer(uniformBuffer, offset: 0, index: 0)

let transformsBufferSize = MemoryLayout<matrix_float4x4>.size * pointTransforms.count
let transformsBuffer = device.makeBuffer(bytes: pointTransforms, length: transformsBufferSize, options:
MTLResourceOptions.cpuCacheModeWriteCombined)
renderEncoder.setVertexBuffer(transformsBuffer, offset: 0, index: 2)

renderEncoder.drawPrimitives(type: .point, vertexStart: 0, vertexCount: self.vertexArray.count / 5)
}
``````

``````struct VertexIn
{
packed_float3  position;
packed_float2  pointPosition;
};

struct VertexOut
{
float4  position [[position]];
float2  pointPosition;
float pointSize [[ point_size ]];
};

struct Uniforms
{
packed_float2 pointTexcoordScale;
float pointSizeInPixel;
};
``````

1. 将输入的位置信息使用运动信息transform进行变换。
3. 把点的像素大小传递给point_size。
``````vertex VertexOut passThroughVertex(uint vid [[ vertex_id ]],
const device VertexIn* vertexIn [[ buffer(0) ]],
const device Uniforms& uniforms [[ buffer(1) ]],
const device float4x4* transforms [[ buffer(2) ]])
{
VertexOut outVertex;
VertexIn inVertex = vertexIn[vid];
outVertex.position = transforms[vid] * float4(inVertex.position, 1.0);
outVertex.pointPosition = inVertex.pointPosition;
outVertex.pointSize = uniforms.pointSizeInPixel;
return outVertex;
};
``````

``````constexpr sampler s(coord::normalized, address::repeat, filter::linear);

fragment float4 passThroughFragment(VertexOut inFrag [[stage_in]],
float2 pointCoord  [[point_coord]],
texture2d<float> diffuse [[ texture(0) ]],
const device Uniforms& uniforms [[ buffer(0) ]])
{
float2 additionUV = float2((pointCoord[0]) * uniforms.pointTexcoordScale[0], (1.0 - pointCoord[1]) * uniforms.pointTexcoordScale[1]);
float2 uv = inFrag.pointPosition + additionUV;
uv = float2(uv[0], 1.0 - uv[1]);
float4 finalColor = diffuse.sample(s, uv);
return finalColor;
};
``````

iOS开发