공통헤더
RederType.h
쉐이더에서 공통적으로 사용할 타입들을 위해 별도 헤더 정의
스위프트 프로젝트에서는 프로젝트.h 에 이 헤더를 import 해주어야 한다.
#include <simd/simd.h>
버텍스 : Vertex
import MetalKit
struct Vertex {
var position: float3
}
쉐이더/텍스처
var vertexFunction:MTLFunction
var fragmentFunction:MTLFunction
var diffuseTexture:MTLTexture
Mesh
var vertexBuffer:MTLBuffer
var indexBuffer:MTLBuffer
초기화
렌더러 생성
simd::float4x4 modelViewProjectionMatrix;
simd::float4x4 modelViewMatrix;
MetalKit clear color 설정
var clearColor: UIColor
self.clearColor = [UIColor clorWithWhite:0.95 alpha:1];
메탈 디바이스
var device:MTLDevice!
device = MTLCreateSystemDefaultDevice();
메탈 레이어
UIView에 렌더링하기 위한 레이어
var metalLayer:CAMetalLayer!
metalLayer = CAMetalLayer()
metalLayer.device = self.device
metalLayer.pixelFormat = .bgra8Unorm
metalLayer.framebufferOnly = true
metalLayer.frame = view.layer.frame
view.layer.addSublayer( metalLayer )
라이브러리
var library: MTLLibrary
library = self.device.makeDeafaultLibrary()
명령큐
var commandQueue: MTLCommandQueue
commandQueue = self.device.makeCommandQueue()
샘플러
var sampler: MTLSamplerState
var samplerDescriptor: MTLSamplerDescriptor
samplerDescriptor = MTLSamplerDescriptor()
samplerDescriptor.minFilter = MTLSamplerMinMagFilter.nearest
samplerDescriptor.magFilger = MTLSamplerMagFilter.linear
samplerDescriptor.sAddressMode = MTLSamplerAddressMode.repeat
samplerDescriptor.tAddressMode = MTLSamplerAddressMode.repeat
sampler = self.device.makeSamplerState( descriptior: samplerDescriptor )
근데 보통은 그냥 쉐이더 샘플러 사용
버텍스
let vertexData: [Vertex]= [ Vertex(0,0,0), Vertex(0,1,0), ..... ]
let indexData:[Int] = [ ..... ]
let dataSize = vertexData.count * MemoryLayout<Vertex>.stride
let indexSize = vertexData.count * MemoryLayout<Int>.size
var vertexBuffer: MTLBuffer!
var indexBuffer: MTLBuffer!
// 버퍼 생성
vertexBuffer = self.device.makeBuffer( bytes: vertexData, length: dataSize, options:MTLResourceOptionCPUCacheModeDefault )
indexBuffer = self.device.makeBuffer( bytes: indexData, length: indexSize, options: MTLResourceOptionCPUCacheModeDefault )
// Mesh 객체에 저장
var mesh: Mesh = Mesh( vertexBuffer:vertexBuffer, indexBuffer:indexBuffer )
쉐이더
var vertexFunction:MTLFunction = self.library.makeFunction( name: "vertex_main" )
var fragmentFunction: MTLFunction = self.library.makeFunction( name: "fragment_main" )
쉐이더 텍스처를 위한 이미지 변환
쉐이더에서 사용할 텍스처 이미지를 생성한다
UIImage는 이미지의 raw 데이터가 접근할 수 없으므로, UIImage로부터 CGImageRef 를 생성
UIKit은 다른 이미지와 마찬가지로 좌상단이 원점이므로
OpenGL 좌표계로 변경하려면 상하를 뒤집어야함
var image: UIImage = UIImage(named:textureName )
CGImageRef imageRef = image.cgImage
NSUInteger width = CGImageGetWidth( imageRef );
NSUInteger height = CGImageGetHeight( imageRef );
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// 이미지 포맷별 상하 뒤집기 위한 부분
uint8_t *rawData = (uint8_t *)calloc(height * width * 4, sizeof(uint8_t);
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitPerComponent = 8;
CGContextRef context =
CGBitmapContextCreate(
rawData,
width,
bitsPerComponent,
bytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextTranslateCTM( context, 0, height )
CGContextScaleCTM( context, 1, -1 );
CGContetDrawImage( context, CGRectMake(0, 0, width, height), imageRef);
CGContextRelease( context );
// 텍스처 생성
var textureDescriptor: MTLTextureDescriptor =
MTLTextureDescriptor.texture2DDescriptor (
pixelFormat:MTLPixelFormat.rgba8Unorm,
width:width,
height:height,
mipmapped:fale
)
var texture: MTLTexture = self.device.makeTexture(descriptior: textureDescriptor )
let region: MTLRegion = MTLRegionMake2D(0, 0, width, height);
texture.replaceRegion( region, mipmapLevel:0 withBytes: rawData, bytesPerRow:bytesPerRow )
// rawData 해제
free( rawData );
var material:Material =
Material(
vertexFunction: vertexFunction,
fragmentFunction: fragmentFunction,
diffuseTexture: texture
)
깊이버퍼 텍스처
var depthTexture:MTLTexture
CGSize drawableSize = self.metalLayer.drawableSize;
var depthDesc: MTLTextureDescriptor =
MTLtextureDescriptor.texture@DDescriptor(
pixelFormat:MTLPixelFormat.depth32Float,
width:drawableSize.width,
height:drawableSize.height,
mipmapped:false
)
self.depthTexture = self.device.makeTexture( descriptor:depthDesc )
버텍스 정보
// MTLVertexDescriptor
// vertex, normal, uv
var vertexDescriptor: MTLVertexDescriptor = MTLVertexDescriptor()
// vertex
vertexDescriptor.attributes[0].format = MTLvertexFormat.float4;
vertexDescriptor.attributes[0].bufferIndex = 0;
vertexDescriptor.attributes[0].offset = MemoryLayout<Vector>.offset(of: \.position )
// normal
vertexDescriptor.attributes[1].format = MTLVertexFormat.float3;
vertexDescriptor.attributes[1].bufferIndex = 0;
vertexDescriptor.attributes[1].offset = MemoryLayout<Vector>.offset(of: \.normal )
// uv
vertexDescriptor.attributes[2].format = MTLVertexFormat.float2;
vertexDescriptor.attributes[2].bufferIndex = 0;
vertexDescriptor.attributes[2].offset = MemoryLayout<Vector>.offset(of: \.texCoords )
vertexDescriptor.layouts[0].stride = MemoryLayout<Vector>.stride
vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunction.perVertex;
Vertex는 쉐이더와 동일하게 정의된 struct 객체
렌더 파이프라인
// MTLRenderPipelineDescriptor
// 쉐이더 함수설정 및 vertex descriptor 설정
var pipelineDescriptor: MTLRenderPipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.vertexFunction = self.material.vertexFunction
pipelineDescriptor.fragmentFunction = self.material.fragmentFunction
pipelineDescriptor.vertexDescritor = vertexDescriptor;
pipelineDescriptor.colorAttachments[0].pixelFormat = MTLPixelFormat.bgra8Unorm;
pipelineDescriptor.depthAttachmentPixelFormat = MTLPixelFormat.depth32Float;
// 렌더 파이프라인 생성
self.pipeline = try? self.device.makeRenderPipelineState( descriptor: pipelineDescriptor )
게임루프
// 생성 : viewDidApper
CADisplayLink
var dispLink: CADisplayLink
dispLink = CADisplayLink( target:self, selector:#selector(dispLinkCallback) )
dispLink.add( to: .current, forMode: .defaultRunLoopMode )
// 콜백
func dispLinkCallback( displaylink: CADisplayLink ) {
// 게임루프 작성
// MVC 변환
// 프레임 업데이트
}
// 해제 : viewWillDisapper
dispLink.invalidata()
게임루프
MVC 변환을 위한 행렬 생성
const CGSize size = self.view.bound.size;
const CGFloat aspectRatio = size.width / size.height;
const CGFloat verticalFOV = (aspectRation > 1) ? 45:90;
static const CGFloat near = 0.1;
static const CGFloat far = 100;
simd::float4x4 projectionMatrix = PerspectiveProjection( aspectRatio, verticalFOV * (M_PI / 180), near, far);
static const simd::float3 X = {1,0,0};
static const simd::float3 Y = {0,1,0};
simd::float4x4 modelViewMatrix = Identity();
modelViewMatrix = modelViewMatrix * Rotation( X, -self.angle.y);
modelViewMatrix = modelViewMatrix * Rotation( Y, -self.angle.x);
modelViewMatrix.columns[3].z = -1.5;
modelViewProjectionMatrix = projectionMatrix * modelViewMatrix;
프레임 그리기
CAMetalDrawable 얻어오기
guard let drawable = self.metalLayer?.nextDrawable() else {
//drawable 얻기 실패
return
}
uniform
// update uniforms
id<MTLBuffer> uniformBuffer;
if( !self.uniformBuffer ) {
self.uniformBuffer = [self.device newBufferWithLength:sizeof(Uniforms) options: MTLResourceOptionCPUCacheModeDefault]
}
Uniforms uniforms;
uniforms.modelViewMatrix = self.modelViewMatrix;
uniforms.modelViewProjectionMatrix = self.modelViewProjectionMatrix;
uniforms.normalMatrix = simd::inverse(simd::transpose(UpperLeft3x3(self.modelViewMatrix)));
memcpy([self.uniformBuffer contents], &uniforms, sizeof(Uniforms));
렌더패스
쉐이더의 렌더 패스를 설정한다.
CGFloat r,g,b,a;
[self.clearColor getRead:&r gree:&g blue:&b alpha:&a];
// MTLRenderPassDescriptor
// texture, depth
var renderPass: MTLRenderPassDescriptor = MTLRenderPassDescriptor()
renderPass.colorAttachments[0].texture = drawable.texture;
renderPass.colorAttachments[0].loadAction = MTLLoadAction.clear;
renderPass.colorAttachments[0].storeAction = MTLStoreAction.store
renderPass.colorAttachments[0].clearColor = MTLClearColorMake(r,g,b,a);
renderPass.depthAttachment.texture = self.depthTexture;
renderPass.depthAttachment.loadAction = MTLLoadAction.clear;
renderPass.depthAttachment.storeAction = MTLStoreAction.store;
renderPass.depthAttachment.clearDepth = 1;
커맨드 인코더
let commandBuffer: MTLCommandBuffer = self.commandQueue.makeCommandBuffer()
var commandEncoder: MTLRenderCommandEncoder = commandBuffer.makeRenderCommandEncoder( descriptor: renderPass )
commandEncoder.setVertexBuffer( mesh.vertexBuffer, offset:0, index:0 )
commandEncoder.setVertexBuffer( self.uniformBuffer, offset:0, index:1 )
commandEncoder.setFragmentBuffer( self.uniformBuffer,offset:0, index:0];
commandEncoder.setFragmentTexture( material.diffuseTexture, index:0 )
commandEncoder.setFragmentSamplerState( self.sampler, index:0 )
commandEncoder.setRenderPipelineState( self.pipeline )
commandEncoder.setCullMode( MTLCullMode.back )
commandEncoder.setFrontFacingWinding ( MTLWinding.counterClockwise )
// MTLDepthStencil
var depthStencilDescriptor: MTLDepthStencilDescriptor = MTLDepthStencilDescriptor()
depthStencilDescriptor.depthCompareFunction = MTLCompareFunction.less;
depthStencilDescriptor.depthWriteEnabled = YES;
var depthStencilState: MTLDepthStencilState = self.device.makeDepthStencilState( descriptor:depthStencilDescriptor )
commandEncoder.setDepthStencilState( depthStencilState )
commandEncoder.drawIndexedPrimitives(
type: MTLPrimitiveType.triangle,
indexCount: mesh.indexBuffer.length() / MemoryLayout<UInt16>().size
indexType: MTLIndexType.uInt16
indexBuffer: mesh.indexBuffer
indexBufferOffset:0
)
commandEncoder.endEncoding()
commandBuffer.commit()
Present
var commandBuffer: MTLCommandBuffer = self.commandQueue.makeCommandBuffer()
commandBuffer.present( drawable )
commandBuffer.commit()
'프로그래밍 > iOS,macOS' 카테고리의 다른 글
iOS 프레임워크 파이썬 스크립트 (0) | 2019.07.23 |
---|---|
[iOS] GPUImage (0) | 2019.07.06 |
xcodebuild (0) | 2019.06.06 |
CocoaPods 라이브러리 배포 (0) | 2019.05.31 |
[Metal] Compute Function 샘플분석 (0) | 2019.05.27 |
swift , iOS 기본 사항만 빠르게 살펴보기 (0) | 2019.05.23 |
swift - objective-c 혼합 사용 (0) | 2019.05.22 |
ios pods google 특이한 에러 (0) | 2017.01.26 |
cocoapods 관련 내용 (0) | 2017.01.10 |
ios 크래시 로그 분석도구 symbolicatecrash 사용하기 (0) | 2016.12.14 |