본문 바로가기
IT/Graphics

Metal 스터디 4주차 (23.01.06)

by YEON-DU 2023. 1. 15.
반응형

Normals

Scene이 원근감과 깊이를 갖고 있지만, 약간의 음영Shading이 있을 때까지는 완전한 3D가 아니다.

우리는 빛의 방향을 설정할 수 있으며 빛에서 멀어지는 Vertex는 빛을 바라보는 Vertex보다 어둡다.

그래서 꼭짓점들이 어느 방향을 바라보고 있는지를 알아야 한다. 표면 법선Surface Normal을 사용하여 이를 알아낸다.

 

Surface Normal은 직각으로 가리키는 Vector로, 표면에 직각이다.

Blender가 OBG 파일을 내보낼 때 파일에 Surface Normal이 포함된다. Surface Normal을 사용하여 표면의 기울기와 방향을 알 수 있다. Normal의 방향을 빛과 음영의 방향과 적절하게 비교할 수 있다.

OBG 파일에서 Normal을 읽도록 코드를 변경해보자.

import Foundation
import MetalKit
extension MTLVertexDescriptor {
    static func defaultVertexDescriptor() -> MTLVertexDescriptor {
        let vertexDescriptor = MTLVertexDescriptor()
        vertexDescriptor.attributes[0].format = .float3
        vertexDescriptor.attributes[0].offset = 0
        vertexDescriptor.attributes[0].bufferIndex = 0
        vertexDescriptor.layouts[0].stride = MemoryLayout<SIMD3<Float>>.stride

                // vertexDescriptor에 Normal을 위한 atrribute 할당.
        vertexDescriptor.attributes[1].format = .float3
        vertexDescriptor.attributes[1].offset = MemoryLayout<SIMD3<Float>>.stride
        vertexDescriptor.attributes[1].bufferIndex = 0

                // 각 vertex가 buffer에서 두 개의 Float3를 가지는 것을 표시.
        vertexDescriptor.layouts[0].stride = MemoryLayout<SIMD3<Float>>.stride * 2

        return vertexDescriptor
    }
}
extension MDLVertexDescriptor {
    static func defaultVertexDescriptor() -> MDLVertexDescriptor {
        let vertexDescriptor = MTKModelIOVertexDescriptorFromMetal(.defaultVertexDescriptor())

        let attributePosition = vertexDescriptor.attributes[0] as! MDLVertexAttribute
        attributePosition.name = MDLVertexAttributePosition
                // Normal은 Model IO에 미리 정의되어 있는 속성 중 하나이다.
        let attributeNormal = vertexDescriptor.attributes[1] as! MDLVertexAttribute
        attributeNormal.name = MDLVertexAttributeNormal

        return vertexDescriptor
    }

 

Ambient and Diffuse Lighting

Ligthing의 경우 발명자의 이름을 딴 Phong이라는 간단한 Shading Model을 사용해본다.

이 Shading Model은 Lighting을 Ambient, Diffuse, Specular로 분할한다.

각 Fragment에 대해 각 Component를 계산하고 모든 Component를 Model의 기본 색상에 추가한다.

예시의 기본 색상은 밝은 회색이다. 조명은 오른쪽 상단에서 나타난다.

Diffuse : Fragment가 빛을 더 많이 바라보고 있을 수록 최종 Diffuse 색상이 기본 색상에 더 가까워진다. Fragment가 빛에서 멀어지면 어두워진다. 빛의 반대 방향을 향하는 조각은 검은색이다.

Ambient : 우리 주변에 있으며 전역 조명Global Illumination에 기여한다. 우리가 빨간불이 켜진 방에 있다면, 각 물체가 어느 방향을 향하고 있든 상관없이 방안의 모든 것이 붉은 색조를 띠어야 한다. 물체에 약간 붉은 빛이 도는 것을 볼 수 있다면 주위에 약간의 붉은 빛이 있음을 암시한다.

Specular Highlight : 개체에서 반사되는 빛이며 개체가 얼마나 빛나는지에 대한 시각적 단서를 제공 한다. 거친 목재 표면에는 Specular가 많지 않은 반면 반짝이는 플라스틱 공에는 매우 강렬한 Specular가 있다.

 

Diffuse Shading의 경우 Light Direction Vector와 Normal Vector를 비교하여 표면에 직교한다.

Normal Vector가 빛과 같은 방향에 가까울수록 더 어두워지고, 반대 방향을 가리키는 경우 가장 밝아진다.

Dot Prodect는 두 Vector 사이의 차이를 알려주는 방법이다.

 

두 Unit Vector를 비교할 때 Unit Vector의 길이는 1단위이다.

Dot 함수의 Return 값은 -1과 1 사이이다. Vector가 같은 방향을 가리키면 결과는 1이다. 반대 방향을 가리키면 결과는 -1이다. 그리고 서로 직각이면 결과는 0이다.

Dot Product는 자주 사용되기 때문에 Metal Shading Language에는 함수가 있다. 따라서 계산 방법을 알지 못해도 dot함수를 사용하면 된다.

 

Shaders.metal

constant float3 lightPosition = float3(2.0, 1.0, 0);
constant float3 ambientLightColor = float3(1.0, 1.0, 1.0);
constant float ambientLightIntensity = 0.3;

fragment float4 fragment_main(VertexOut in [[stage_in]]) {
    float3 lightVector = normalize(lightPosition);
    float3 normalVector = normalize(in.worldNormal);

    float3 baseColor = in.color;

    float diffuseIntensity = saturate(dot(lightVector, normalVector));

    float3 diffuseColor = baseColor * diffuseIntensity;
    float3 ambientColor = baseColor * ambientLightColor * ambientLightIntensity;

    float3 color = diffuseColor + ambientColor;
    return float4(color, 1);
}

 

Specular Lighting

Lighting을 보다 사실적으로 만들기 위해 반사광Specular을 추가한다.

반사광은 빛과 Normal을 계산에 사용한다는 점에서 Diffuse과 유사하다. 그러나 Specular Shading을 사용하면 카메라 위치도 고려해야 한다.

 

Specular Reflection은 Camera Vector 또는 눈 위치, Light Vector 및 Normal Vector를 사용한다.

실생활에서 반사광은 물체에서 반사되어 눈으로 들어오는 빛에서 나온다.

Surface Normal에 대한 Light Vector를 반사하는 Vector를 만들 것이다. Metal Shading Language는 이 Reflection Vector를 생성하기 위한 반사 기능을 제공한다.

Camera Vector가 해당 Reflection Vector에 가까울수록 Specular Highlight가 더 강해진다. Surface는 Shininess가 다르다.

자동차는 매우 반짝이지만 타이어는 전혀 빛나지 않는다.

Shininess 속성이 있는 거듭제곱 함수를 사용한다. Shininess 속성이 높을수록 Specular Highlight가 더 높아진다.

 

Shaders.metal

fragment float4 fragment_main(VertexOut in [[stage_in]],
                              constant FragmentUniforms &fragmentUniforms [[buffer(22)]]) {
    float3 lightVector = normalize(lightPosition);
    float3 normalVector = normalize(in.worldNormal);

        // 아직 material을 읽어오지 않았으므로 임의의 Shininess를 지정
    float materialShininess = 32;
    float3 materialSpecularColor = float3(1.0, 1.0, 1.0);

    float3 baseColor = in.color;

    float diffuseIntensity = saturate(dot(lightVector, normalVector));

    float3 diffuseColor = baseColor * diffuseIntensity;
    float3 ambientColor = baseColor * ambientLightColor * ambientLightIntensity;

        // 반사되는 specular 색상은 흰색이 된다.
    float3 reflection = reflect(lightVector, normalVector);

        // specular를 구하기 위해서 카메라에 대한 Fragment의 방향이 필요하다.
        // Point를 서로 빼면, 두 번째 Point에서 첫 번째 Point을 가리키는 Vector가 된다.
    float3 cameraVector = normalize(in.worldPosition - fragmentUniforms.cameraPosition);
    float specularIntensity = pow(saturate(dot(reflection, cameraVector)), materialShininess);
    float3 specularColor = lightSpecularColor * materialSpecularColor * specularIntensity;

    float3 color = diffuseColor + ambientColor + specularColor;
    return float4(color, 1);
}

 

Scene Graph

게임 엔진을 구성하기 위해 기존 구조를 변경해본다.

Scene Class를 생성하여 Rendering에서 모든 Model이 포함된 Scene을 분리한다.

Scene Class는 Model을 추가하고 키보드 명령을 사용하여 Model과 상호 작용하거나 iOS에서 Gesture를 사용하는 경우와 게임 로직을 작성할 때 사용한다.

 

게임에서 Tree, House 및 Animation Character를 원할 수 있다. 각각 고유한 변환 속성이 있는 Node 계층 구조로 구성된 Scene Graph를 생성한다.

Scene Graph는 Scene에 있는 개체의 공간 관계를 정의한다.

각 Node는 Root Node가 아닌 한 하나의 부모와 여러 자식을 가진다.

이 예제에서 Root Node에는 4개의 자식이 있다. Driver에는 부모가 있지만 자식은 없다. 이 Scene Graph에서 자동차와 운전자를 예로 들어본다.

운전자는 차 안에 있지 않을 때 차와 독립적으로 움직일 수 있다.

이 경우 Driver의 Transform 속성을 변경한다.

그러나 차가 움직일 때 운전자가 차 안에 있으면 운전자는 차에 대해 동일한 위치에 머문다.

이 경우 자동차의 Transform 속성을 변경한다.

운전자는 자동차의 자식 Node이기 때문에 부모의 Transform 속성을 사용한다.

 

Create a Scene

GameScene.swift

import Foundation

class GameScene: Scene {
    let train = Model(name: "train")
    let tree = Model(name: "treefir")

    override func setupScene() {
        add(node: train)
        add(node: tree)
        tree.position.x = -2.0
    }
}

Node.swift

import Foundation

class Node {
    var name = "Untitled"

    var position = SIMD3<Float>(repeating: 0)
    var rotation = SIMD3<Float>(repeating: 0)
    var scale: Float = 1

    var matrix: float4x4  {
        let translateMatrix = float4x4(translation: position)
        let rotationMatrix = float4x4(rotation: rotation)
        let scaleMatrix = float4x4(scaling: scale)
        return translateMatrix * scaleMatrix * rotationMatrix
    }

    var parent: Node?
    var children: [Node] = []

    var worldMatrix: float4x4 {
        if let parent = parent {
            return parent.worldMatrix * matrix
        }
        return matrix
    }

    final func add(childNode: Node) {
        children.append(childNode)
        childNode.parent = self
    }

    final func remove(childNode: Node) {
        for child in childNode.children {
            child.parent = self
            children.append(child)
        }
        childNode.children = []
        guard let index = (children.firstIndex {
            $0 === childNode
        }) else { return }
        children.remove(at: index)
    }
}

 

Conclusion

지금까지 작성한 코드로 이미 간단한 게임을 구성할 수 있다.

Scene에 Model을 추가하고 Game Logic로 Model을 이동할 수 있는 Scene Class가 있다.

 

Scene에 조명을 추가하고, Scene Graph를 만들었다.

또한 좌표 공간과 행렬을 사용하여 Model을 이동하는 개념도 배웠다. 이러한 개념을 이해하기 위해서는 Grant Sanderson의 Essence of Linear Algebra를 시청하는 것이 좋다. 이 YouTube 시리즈는 애니메이션 시리즈이며 행렬과 벡터에 대한 기하학적 이해를 제공한다.

반응형

'IT > Graphics' 카테고리의 다른 글

Metal 스터디 6주차 (23.01.19)  (0) 2023.02.06
Metal 스터디 5주차 (23.01.12)  (0) 2023.01.15
Metal 스터디 3주차 (22.12.22)  (0) 2022.12.28
Metal 스터디 2주차 (22.12.15)  (0) 2022.12.28
Metal 스터디 1주차 (22.12.09)  (0) 2022.12.13

댓글