From 428b142bf20c87081e0ddd999c4dfe7cb366d3b1 Mon Sep 17 00:00:00 2001 From: a dinosaur Date: Sun, 25 Aug 2024 14:49:07 +1000 Subject: [PATCH] un-hardcode various render properties (environment, material) --- Sources/Voxelotl/Application.swift | 2 + Sources/Voxelotl/CMakeLists.txt | 7 +- Sources/Voxelotl/Game.swift | 15 +++- Sources/Voxelotl/GameDelegate.swift | 1 + Sources/Voxelotl/Player.swift | 2 +- Sources/Voxelotl/Renderer/Environment.swift | 6 ++ Sources/Voxelotl/Renderer/Material.swift | 7 ++ .../Voxelotl/{ => Renderer}/Renderer.swift | 82 +++++++++++-------- 8 files changed, 85 insertions(+), 37 deletions(-) create mode 100644 Sources/Voxelotl/Renderer/Environment.swift create mode 100644 Sources/Voxelotl/Renderer/Material.swift rename Sources/Voxelotl/{ => Renderer}/Renderer.swift (81%) diff --git a/Sources/Voxelotl/Application.swift b/Sources/Voxelotl/Application.swift index 163d642..d89bb2d 100644 --- a/Sources/Voxelotl/Application.swift +++ b/Sources/Voxelotl/Application.swift @@ -57,6 +57,8 @@ public class Application { printErr("Renderer init error: unexpected error") } + self.del.create(renderer!) + lastCounter = SDL_GetPerformanceCounter() return .running } diff --git a/Sources/Voxelotl/CMakeLists.txt b/Sources/Voxelotl/CMakeLists.txt index fefc8e4..e3bcd7c 100644 --- a/Sources/Voxelotl/CMakeLists.txt +++ b/Sources/Voxelotl/CMakeLists.txt @@ -26,6 +26,11 @@ add_executable(Voxelotl MACOSX_BUNDLE # Resource classes NSImageLoader.swift + # Renderer classes + Renderer/Material.swift + Renderer/Environment.swift + Renderer/Renderer.swift + # Input wrappers Input/Keyboard.swift Input/GameController.swift @@ -34,7 +39,6 @@ add_executable(Voxelotl MACOSX_BUNDLE # Core utility classes Color.swift Camera.swift - Renderer.swift FPSCalculator.swift GameDelegate.swift Application.swift @@ -85,3 +89,4 @@ source_group("Source Files" REGULAR_EXPRESSION "\\.(swift|metal)$") source_group("Source Files\\Random" REGULAR_EXPRESSION "Random/") source_group("Source Files\\Math" REGULAR_EXPRESSION "Math/") source_group("Source Files\\Input" REGULAR_EXPRESSION "Input/") +source_group("Source Files\\Renderer" REGULAR_EXPRESSION "Renderer/") diff --git a/Sources/Voxelotl/Game.swift b/Sources/Voxelotl/Game.swift index 6b383a3..79be5a8 100644 --- a/Sources/Voxelotl/Game.swift +++ b/Sources/Voxelotl/Game.swift @@ -26,9 +26,11 @@ class Game: GameDelegate { var projection: matrix_float4x4 = .identity var chunk = Chunk(position: .zero) - init() { + func create(_ renderer: Renderer) { self.resetPlayer() self.generateWorld() + + renderer.clearColor = Color.black.mix(.white, 0.1).linear } private func resetPlayer() { @@ -103,6 +105,15 @@ class Game: GameDelegate { func draw(_ renderer: Renderer, _ time: GameTime) { let totalTime = Float(time.total.asFloat) + let env = Environment( + cullFace: .back, + lightDirection: .init(0.75, -1, 0.5)) + let material = Material( + ambient: Color(rgba8888: 0x4F4F4F00).linear, + diffuse: Color(rgba8888: 0xDFDFDF00).linear, + specular: Color(rgba8888: 0x2F2F2F00).linear, + gloss: 75) + var instances = chunk.compactMap { block, position in if case let .solid(color) = block.type { Instance( @@ -121,7 +132,7 @@ class Game: GameDelegate { .init(angle: totalTime * 0.7, axis: .init(0, 0, 1)), color: .init(r: 0.5, g: 0.5, b: 1).linear)) if !instances.isEmpty { - renderer.batch(instances: instances, camera: self.camera) + renderer.batch(instances: instances, material: material, environment: env, camera: self.camera) } } diff --git a/Sources/Voxelotl/GameDelegate.swift b/Sources/Voxelotl/GameDelegate.swift index eece92a..61fdcf2 100644 --- a/Sources/Voxelotl/GameDelegate.swift +++ b/Sources/Voxelotl/GameDelegate.swift @@ -1,4 +1,5 @@ public protocol GameDelegate { + func create(_ renderer: Renderer) func fixedUpdate(_ time: GameTime) func update(_ time: GameTime) func draw(_ renderer: Renderer, _ time: GameTime) diff --git a/Sources/Voxelotl/Player.swift b/Sources/Voxelotl/Player.swift index 9fe16b4..514a7b1 100644 --- a/Sources/Voxelotl/Player.swift +++ b/Sources/Voxelotl/Player.swift @@ -203,7 +203,7 @@ struct Player { chunk: chunk, origin: self.eyePosition, direction: .forward * simd_matrix3x3(self.eyeRotation), - maxDistance: 3.666 + maxDistance: 3.8 ) { self.rayhitPos = hit.position if destroy { diff --git a/Sources/Voxelotl/Renderer/Environment.swift b/Sources/Voxelotl/Renderer/Environment.swift new file mode 100644 index 0000000..6481ba0 --- /dev/null +++ b/Sources/Voxelotl/Renderer/Environment.swift @@ -0,0 +1,6 @@ +public struct Environment { + public var cullFace: Face + public var lightDirection: SIMD3 + + public enum Face { case none, front, back } +} diff --git a/Sources/Voxelotl/Renderer/Material.swift b/Sources/Voxelotl/Renderer/Material.swift new file mode 100644 index 0000000..2f1c849 --- /dev/null +++ b/Sources/Voxelotl/Renderer/Material.swift @@ -0,0 +1,7 @@ + +public struct Material { + public var ambient: Color + public var diffuse: Color + public var specular: Color + public var gloss: Float +} diff --git a/Sources/Voxelotl/Renderer.swift b/Sources/Voxelotl/Renderer/Renderer.swift similarity index 81% rename from Sources/Voxelotl/Renderer.swift rename to Sources/Voxelotl/Renderer/Renderer.swift index 7473381..7d2a93a 100644 --- a/Sources/Voxelotl/Renderer.swift +++ b/Sources/Voxelotl/Renderer/Renderer.swift @@ -5,30 +5,30 @@ import simd import ShaderTypes fileprivate let cubeVertices: [ShaderVertex] = [ - .init(position: .init(-1, -1, 1, 1), normal: .init( 0, 0, 1, 0), texCoord: .init(0, 0)), - .init(position: .init( 1, -1, 1, 1), normal: .init( 0, 0, 1, 0), texCoord: .init(1, 0)), - .init(position: .init(-1, 1, 1, 1), normal: .init( 0, 0, 1, 0), texCoord: .init(0, 1)), - .init(position: .init( 1, 1, 1, 1), normal: .init( 0, 0, 1, 0), texCoord: .init(1, 1)), - .init(position: .init( 1, -1, 1, 1), normal: .init( 1, 0, 0, 0), texCoord: .init(0, 0)), - .init(position: .init( 1, -1, -1, 1), normal: .init( 1, 0, 0, 0), texCoord: .init(1, 0)), - .init(position: .init( 1, 1, 1, 1), normal: .init( 1, 0, 0, 0), texCoord: .init(0, 1)), - .init(position: .init( 1, 1, -1, 1), normal: .init( 1, 0, 0, 0), texCoord: .init(1, 1)), - .init(position: .init( 1, -1, -1, 1), normal: .init( 0, 0, -1, 0), texCoord: .init(0, 0)), - .init(position: .init(-1, -1, -1, 1), normal: .init( 0, 0, -1, 0), texCoord: .init(1, 0)), - .init(position: .init( 1, 1, -1, 1), normal: .init( 0, 0, -1, 0), texCoord: .init(0, 1)), - .init(position: .init(-1, 1, -1, 1), normal: .init( 0, 0, -1, 0), texCoord: .init(1, 1)), - .init(position: .init(-1, -1, -1, 1), normal: .init(-1, 0, 0, 0), texCoord: .init(0, 0)), - .init(position: .init(-1, -1, 1, 1), normal: .init(-1, 0, 0, 0), texCoord: .init(1, 0)), - .init(position: .init(-1, 1, -1, 1), normal: .init(-1, 0, 0, 0), texCoord: .init(0, 1)), - .init(position: .init(-1, 1, 1, 1), normal: .init(-1, 0, 0, 0), texCoord: .init(1, 1)), - .init(position: .init(-1, -1, -1, 1), normal: .init( 0, -1, 0, 0), texCoord: .init(0, 0)), - .init(position: .init( 1, -1, -1, 1), normal: .init( 0, -1, 0, 0), texCoord: .init(1, 0)), - .init(position: .init(-1, -1, 1, 1), normal: .init( 0, -1, 0, 0), texCoord: .init(0, 1)), - .init(position: .init( 1, -1, 1, 1), normal: .init( 0, -1, 0, 0), texCoord: .init(1, 1)), - .init(position: .init(-1, 1, 1, 1), normal: .init( 0, 1, 0, 0), texCoord: .init(0, 0)), - .init(position: .init( 1, 1, 1, 1), normal: .init( 0, 1, 0, 0), texCoord: .init(1, 0)), - .init(position: .init(-1, 1, -1, 1), normal: .init( 0, 1, 0, 0), texCoord: .init(0, 1)), - .init(position: .init( 1, 1, -1, 1), normal: .init( 0, 1, 0, 0), texCoord: .init(1, 1)), + .init(position: .init(-1, -1, 1, 1), normal: .init(.back, 0), texCoord: .init(0, 0)), + .init(position: .init( 1, -1, 1, 1), normal: .init(.back, 0), texCoord: .init(1, 0)), + .init(position: .init(-1, 1, 1, 1), normal: .init(.back, 0), texCoord: .init(0, 1)), + .init(position: .init( 1, 1, 1, 1), normal: .init(.back, 0), texCoord: .init(1, 1)), + .init(position: .init( 1, -1, 1, 1), normal: .init(.right, 0), texCoord: .init(0, 0)), + .init(position: .init( 1, -1, -1, 1), normal: .init(.right, 0), texCoord: .init(1, 0)), + .init(position: .init( 1, 1, 1, 1), normal: .init(.right, 0), texCoord: .init(0, 1)), + .init(position: .init( 1, 1, -1, 1), normal: .init(.right, 0), texCoord: .init(1, 1)), + .init(position: .init( 1, -1, -1, 1), normal: .init(.forward, 0), texCoord: .init(0, 0)), + .init(position: .init(-1, -1, -1, 1), normal: .init(.forward, 0), texCoord: .init(1, 0)), + .init(position: .init( 1, 1, -1, 1), normal: .init(.forward, 0), texCoord: .init(0, 1)), + .init(position: .init(-1, 1, -1, 1), normal: .init(.forward, 0), texCoord: .init(1, 1)), + .init(position: .init(-1, -1, -1, 1), normal: .init(.left, 0), texCoord: .init(0, 0)), + .init(position: .init(-1, -1, 1, 1), normal: .init(.left, 0), texCoord: .init(1, 0)), + .init(position: .init(-1, 1, -1, 1), normal: .init(.left, 0), texCoord: .init(0, 1)), + .init(position: .init(-1, 1, 1, 1), normal: .init(.left, 0), texCoord: .init(1, 1)), + .init(position: .init(-1, -1, -1, 1), normal: .init(.down, 0), texCoord: .init(0, 0)), + .init(position: .init( 1, -1, -1, 1), normal: .init(.down, 0), texCoord: .init(1, 0)), + .init(position: .init(-1, -1, 1, 1), normal: .init(.down, 0), texCoord: .init(0, 1)), + .init(position: .init( 1, -1, 1, 1), normal: .init(.down, 0), texCoord: .init(1, 1)), + .init(position: .init(-1, 1, 1, 1), normal: .init(.up, 0), texCoord: .init(0, 0)), + .init(position: .init( 1, 1, 1, 1), normal: .init(.up, 0), texCoord: .init(1, 0)), + .init(position: .init(-1, 1, -1, 1), normal: .init(.up, 0), texCoord: .init(0, 1)), + .init(position: .init( 1, 1, -1, 1), normal: .init(.up, 0), texCoord: .init(1, 1)), ] fileprivate let cubeIndices: [UInt16] = [ @@ -43,12 +43,12 @@ fileprivate let cubeIndices: [UInt16] = [ fileprivate let numFramesInFlight: Int = 3 fileprivate let colorFormat: MTLPixelFormat = .bgra8Unorm_srgb fileprivate let depthFormat: MTLPixelFormat = .depth32Float -fileprivate let clearColor: Color = .black.mix(.white, 0.1).linear public class Renderer { private var device: MTLDevice private var layer: CAMetalLayer private var backBufferSize: Size + private var _clearColor: Color private var _aspectRatio: Float private var queue: MTLCommandQueue private var lib: MTLLibrary @@ -70,6 +70,10 @@ public class Renderer { var frame: Rect { .init(origin: .zero, size: self.backBufferSize) } var aspectRatio: Float { self._aspectRatio } + var clearColor: Color { + get { self._clearColor } + set { self._clearColor = newValue } + } fileprivate static func createMetalDevice() -> MTLDevice? { MTLCopyAllDevices().reduce(nil, { best, dev in @@ -100,10 +104,10 @@ public class Renderer { self.backBufferSize = size self._aspectRatio = Float(self.backBufferSize.w) / Float(self.backBufferSize.w) + self._clearColor = .black passDescription.colorAttachments[0].loadAction = .clear passDescription.colorAttachments[0].storeAction = .store - passDescription.colorAttachments[0].clearColor = MTLClearColor(clearColor) passDescription.depthAttachment.loadAction = .clear passDescription.depthAttachment.storeAction = .dontCare passDescription.depthAttachment.clearDepth = 1.0 @@ -302,6 +306,7 @@ public class Renderer { throw RendererError.drawFailure("Failed to get next drawable render target") } + passDescription.colorAttachments[0].clearColor = MTLClearColor(self._clearColor) passDescription.colorAttachments[0].texture = rt.texture passDescription.depthAttachment.texture = self.depthTextures[self.currentFrame] @@ -319,7 +324,6 @@ public class Renderer { throw RendererError.drawFailure("Failed to make render encoder from command buffer") } - encoder.setCullMode(.back) encoder.setFrontFacing(.counterClockwise) // OpenGL default encoder.setViewport(Self.makeViewport(rect: self.frame)) encoder.setRenderPipelineState(pso) @@ -342,17 +346,17 @@ public class Renderer { } } - func batch(instances: [Instance], camera: Camera) { + func batch(instances: [Instance], material: Material, environment: Environment, camera: Camera) { assert(self._encoder != nil, "batch can't be called outside of a frame being rendered") var vertUniforms = VertexShaderUniforms(projView: camera.viewProjection) var fragUniforms = FragmentShaderUniforms( cameraPosition: camera.position, - directionalLight: normalize(.init(0.75, -1, 0.5)), - ambientColor: SIMD4(Color(rgba8888: 0x1F1F1F00).linear), - diffuseColor: SIMD4(Color(rgba8888: 0xEFEFEF00).linear), - specularColor: SIMD4(Color(rgba8888: 0x7F7F7F00).linear), - specularIntensity: 50) + directionalLight: normalize(environment.lightDirection), + ambientColor: SIMD4(Color(material.ambient)), + diffuseColor: SIMD4(Color(material.diffuse)), + specularColor: SIMD4(Color(material.specular)), + specularIntensity: material.gloss) let numInstances = instances.count let instancesBytes = numInstances * MemoryLayout.stride @@ -385,6 +389,8 @@ public class Renderer { } instanceBuffer.didModifyRange(0..