diff --git a/assets/faux_render_shaders/render_pbr_material.metal b/assets/faux_render_shaders/render_pbr_material.metal new file mode 100644 index 00000000..010e13eb --- /dev/null +++ b/assets/faux_render_shaders/render_pbr_material.metal @@ -0,0 +1,396 @@ +#include +using namespace metal; + +#define PI 3.1415292 +#define EPSILON 0.00001 + +#define MAX_INSTANCES 100 +#define MAX_MATERIALS 100 +#define MAX_MATERIAL_SAMPLERS 32 +#define MAX_MATERIAL_IMAGES 1024 +#define MAX_IBL_TEXTURES 1 + +#define SCENE_REGISTER 4 +#define CAMERA_REGISTER 5 +#define DRAW_REGISTER 6 +#define INSTANCE_BUFFER_REGISTER 7 +#define MATERIAL_BUFFER_REGISTER 8 +#define MATERIAL_SAMPLER_START_REGISTER 9 +#define MATERIAL_IMAGES_START_REGISTER 10 +#define IBL_ENV_MAP_TEXTURE_START_REGISTER 11 +#define IBL_IRR_MAP_TEXTURE_START_REGISTER 12 +#define IBL_INTEGRATION_LUT_REGISTER 13 +#define IBL_MAP_SAMPLER_REGISTER 14 +#define IBL_INTEGRATION_SAMPLER_REGISTER 15 + +enum MaterialFlagBits +{ + MATERIAL_FLAG_BASE_COLOR_TEXTURE = (1 << 1), + MATERIAL_FLAG_METALLIC_ROUGHNESS_TEXTURE = (1 << 2), + MATERIAL_FLAG_NORMAL_TEXTURE = (1 << 3), + MATERIAL_FLAG_OCCLUSION_TEXTURE = (1 << 4), + MATERIAL_FLAG_EMISSIVE_TEXTURE = (1 << 5), +}; + +struct SceneData +{ + uint32_t IBLEnvironmentNumLevels; +}; + +struct CameraData +{ + float4x4 ViewProjectionMatrix; + float3 EyePosition; +}; + +struct DrawData +{ + uint InstanceIndex; + uint MaterialIndex; +}; + +struct InstanceData +{ + float4x4 ModelMatrix; + float4x4 NormalMatrix; +}; + +struct TextureData +{ + uint ImageIndex; + uint SamplerIndex; +}; + +struct MaterialData +{ + uint MaterialFlags; + float3 BaseColor; + float MetallicFactor; + float RoughnessFactor; + uint2 BaseColorTexture; + uint2 MetallicRoughnessTexture; + uint2 NormalTexture; + uint2 OcclusionTexture; + uint2 EmissiveTexture; + + float2 TexCoordTranslate; + float2 TexCoordScale; + float TexCoordRotate; +}; + +struct MaterialImageArray +{ + array, MAX_MATERIAL_IMAGES> Images; +}; + +struct MaterialSamplerArray +{ + array Samplers; +}; + +struct IBLIrrMapTextureArray +{ + array, MAX_IBL_TEXTURES> Textures; +}; + +struct IBLEnvMapTextureArray +{ + array, MAX_IBL_TEXTURES> Textures; +}; + +struct VSOutput +{ + float4 PositionWS; + float4 PositionCS [[position]]; + float2 TexCoord; + float3 Normal; + float4 Tangent; +}; + +struct VertexData +{ + float3 PositionOS [[attribute(0)]]; + float2 TexCoord [[attribute(1)]]; + float3 Normal [[attribute(2)]]; + float4 Tangent [[attribute(3)]]; +}; + +VSOutput vertex vsmain( + VertexData vertexData [[stage_in]], + constant DrawData& Draw [[buffer(DRAW_REGISTER)]], + constant CameraData& Camera [[buffer(CAMERA_REGISTER)]], + constant InstanceData* Instances [[buffer(INSTANCE_BUFFER_REGISTER)]]) +{ + InstanceData instance = Instances[Draw.InstanceIndex]; + + VSOutput output; + output.PositionWS = (instance.ModelMatrix * float4(vertexData.PositionOS, 1)); + output.PositionCS = (Camera.ViewProjectionMatrix * output.PositionWS); + output.TexCoord = vertexData.TexCoord; + output.Normal = vertexData.Normal; + output.Tangent = vertexData.Tangent; + return output; +} + +float3x3 CalculateTexCoordTransform(float2 translate, float rotate, float2 scale) +{ + float3x3 T = float3x3( + 1, 0, 0, 0, 1, 0, translate.x, translate.y, 1); + + float cs = cos(rotate); + float sn = sin(rotate); + float3x3 R = float3x3( + cs, sn, 0, -sn, cs, 0, 0, 0, 1); + + float3x3 S = float3x3( + scale.x, 0, 0, 0, scale.y, 0, 0, 0, 1); + + return T * R * S; +} + +float3 Fresnel_SchlickRoughness(float cosTheta, float3 F0, float roughness) +{ + float3 r = (float3)(1 - roughness); + return F0 + (max(r, F0) - F0) * pow(1 - cosTheta, 5); +} + +float Fd_Lambert() +{ + return 1.0 / PI; +} + +// circular atan2 - converts (x,y) on a unit circle to [0, 2pi] +// +#define catan2_epsilon 0.00001 +#define catan2_NAN 0.0 / 0.0 // No gaurantee this is correct + +float catan2(float y, float x) +{ + float absx = abs(x); + float absy = abs(y); + if ((absx < catan2_epsilon) && (absy < catan2_epsilon)) + { + return catan2_NAN; + } + else if ((absx > 0) && (absy == 0.0)) + { + return 0.0; + } + float s = 1.5 * 3.141592; + if (y >= 0) + { + s = 3.141592 / 2.0; + } + return s - atan(x / y); +} + +// Converts cartesian unit position 'pos' to (theta, phi) in +// spherical coodinates. +// +// theta is the azimuth angle between [0, 2pi]. +// phi is the polar angle between [0, pi]. +// +// NOTE: (0, 0, 0) will result in nan +// +float2 CartesianToSpherical(float3 pos) +{ + float absX = abs(pos.x); + float absZ = abs(pos.z); + // Handle pos pointing straight up or straight down + if ((absX < 0.00001) && (absZ <= 0.00001)) + { + // Pointing straight up + if (pos.y > 0) + { + return float2(0, 0); + } + // Pointing straight down + else if (pos.y < 0) + { + return float2(0, 3.141592); + } + // Something went terribly wrong + else + { + return float2(catan2_NAN, catan2_NAN); + } + } + float theta = catan2(pos.z, pos.x); + float phi = acos(pos.y); + return float2(theta, phi); +} + +float3 GetIBLIrradiance(texture2d IBLIrrMapTexture, sampler IBLMapSampler, float3 dir) +{ + float2 uv = CartesianToSpherical(normalize(dir)); + uv.x = saturate(uv.x / (2.0 * PI)); + uv.y = saturate(uv.y / PI); + float3 color = IBLIrrMapTexture.sample(IBLMapSampler, uv, level(0)).rgb; + return color; +} + +float3 GetIBLEnvironment(texture2d IBLEnvMapTexture, sampler IBLMapSampler, float3 dir, float lod) +{ + float2 uv = CartesianToSpherical(normalize(dir)); + uv.x = saturate(uv.x / (2.0 * PI)); + uv.y = saturate(uv.y / PI); + float3 color = IBLEnvMapTexture.sample(IBLMapSampler, uv, level(lod)).rgb; + return color; +} + +float2 GetBRDFIntegrationMap(texture2d IBLIntegrationLUT, sampler IBLIntegrationSampler, float roughness, float NoV) +{ + float2 tc = float2(saturate(roughness), saturate(NoV)); + float2 brdf = IBLIntegrationLUT.sample(IBLIntegrationSampler, tc).rg; + return brdf; +} + +/* +float2 GetBRDFIntegrationMultiscatterMap(texture2d IBLIntegrationMultiscatterLUT, sampler IBLIntegrationSampler, float roughness, float NoV) +{ + float2 tc = float2(saturate(roughness), saturate(NoV)); + float2 brdf = IBLIntegrationMultiscatterLUT.sample(IBLIntegrationSampler, tc).rg; + return brdf; +}*/ + +float4 fragment psmain( + VSOutput input [[stage_in]], + constant SceneData& Scene [[buffer(SCENE_REGISTER)]], + constant DrawData& Draw [[buffer(DRAW_REGISTER)]], + constant CameraData& Camera [[buffer(CAMERA_REGISTER)]], + constant InstanceData* Instances [[buffer(INSTANCE_BUFFER_REGISTER)]], + constant MaterialData* Materials [[buffer(MATERIAL_BUFFER_REGISTER)]], + constant MaterialImageArray* MaterialImages [[buffer(MATERIAL_IMAGES_START_REGISTER)]], + constant MaterialSamplerArray* MaterialSamplers [[buffer(MATERIAL_SAMPLER_START_REGISTER)]], + constant IBLEnvMapTextureArray* IBLEnvMap [[buffer(IBL_ENV_MAP_TEXTURE_START_REGISTER)]], + constant IBLIrrMapTextureArray* IBLIrrMap [[buffer(IBL_IRR_MAP_TEXTURE_START_REGISTER)]], + texture2d IBLIntegrationLUT [[texture(IBL_INTEGRATION_LUT_REGISTER)]], + sampler IBLMapSampler [[sampler(IBL_MAP_SAMPLER_REGISTER)]], + sampler IBLIntegrationSampler [[sampler(IBL_INTEGRATION_SAMPLER_REGISTER)]]) +{ + InstanceData instance = Instances[Draw.InstanceIndex]; + MaterialData material = Materials[Draw.MaterialIndex]; + + // Transform UV to match material + float3x3 uvTransform = CalculateTexCoordTransform(material.TexCoordTranslate, material.TexCoordRotate, material.TexCoordScale); + float2 uv = (uvTransform * float3(input.TexCoord, 1)).xy; + + // Base color + float3 baseColor = material.BaseColor; + if (material.MaterialFlags & MATERIAL_FLAG_BASE_COLOR_TEXTURE) + { + TextureData params = {material.BaseColorTexture.x, material.BaseColorTexture.y}; + + texture2d tex = MaterialImages->Images[params.ImageIndex]; + sampler samp = MaterialSamplers->Samplers[params.SamplerIndex]; + baseColor = tex.sample(samp, uv).rgb; + } + + // Metallic and roughness + // + // From GLTF spec (material.pbrMetallicRoughness.metallicRoughnessTexture): + // The metalness values are sampled from the B channel. + // The roughness values are sampled from the G channel + // + float metallic = material.MetallicFactor; + float roughness = material.RoughnessFactor; + if (material.MaterialFlags & MATERIAL_FLAG_METALLIC_ROUGHNESS_TEXTURE) + { + TextureData params = {material.MetallicRoughnessTexture.x, material.MetallicRoughnessTexture.y}; + + texture2d tex = MaterialImages->Images[params.ImageIndex]; + sampler samp = MaterialSamplers->Samplers[params.SamplerIndex]; + float3 color = tex.sample(samp, uv).rgb; + metallic = metallic * color.b; + roughness = roughness * color.g; + } + + // Normal (N) + float3 N = (instance.NormalMatrix * float4(input.Normal, 0)).xyz; + if (material.MaterialFlags & MATERIAL_FLAG_NORMAL_TEXTURE) + { + TextureData params = {material.NormalTexture.x, material.NormalTexture.y}; + + texture2d tex = MaterialImages->Images[params.ImageIndex]; + sampler samp = MaterialSamplers->Samplers[params.SamplerIndex]; + float3 vNt = tex.sample(samp, uv).rgb; + vNt = normalize((2.0 * vNt) - 1.0); + + float3 vN = N; + float3 vT = (instance.NormalMatrix * float4(input.Tangent.xyz, 0)).xyz; + float3 vB = cross(vN, vT) * input.Tangent.w; + N = normalize(vNt.x * vT + vNt.y * vB + vNt.z * vN); + } + + // Scene and geometry variables - world space + float3 P = input.PositionWS.xyz; // Position + float3 V = normalize((Camera.EyePosition - P)); // View direction + + // Specular and dieletric + float specular = 0.5; + float dielectric = 1.0 - metallic; + + // Remove gamma + baseColor = pow(baseColor, 2.2); + + // Remap + float3 diffuseColor = dielectric * baseColor; + float alpha = roughness; // * roughness; + + // Calculate F0 + float3 F0 = (0.16 * specular * specular * dielectric) + (baseColor * metallic); + + // Reflection and cosine angle between N and V + float3 R = reflect(-V, N); + float NoV = saturate(dot(N, V)); + + // Indirect lighting + float3 indirectLighting = (float3)0; + { + float3 F = Fresnel_SchlickRoughness(NoV, F0, alpha); + float3 Kd = (1 - F) * dielectric; + + float3 Rr = R; + /* + if (anisotropy != 0.0) { + Rr = GetReflectedVector(V, N, vT, vB, alpha, anisotropy); + } + */ + + // Diffuse IBL component + float3 irradiance = GetIBLIrradiance(IBLIrrMap->Textures[0], IBLMapSampler, N); + float3 Rd = irradiance * diffuseColor * Fd_Lambert(); + + // Specular IBL component + float lod = alpha * (Scene.IBLEnvironmentNumLevels - 1); + float3 prefilteredColor = GetIBLEnvironment(IBLEnvMap->Textures[0], IBLMapSampler, Rr, lod); + float2 envBRDF = (float2)0; + float3 Rs = (float3)0; + /* + if (SceneParams.Multiscatter) { + envBRDF = GetBRDFIntegrationMultiscatterMap(IBLIintegrationMultiscatterLUT, IBLIntegratoinSampler, NoV, alpha); + Rs = prefilteredColor * lerp(envBRDF.xxx, envBRDF.yyy, F0); + + float3 energyCompensation = 1.0 + F0 * (1.0 / envBRDF.y - 1.0); + Rs *= energyCompensation; + } + else + */ + { + envBRDF = GetBRDFIntegrationMap(IBLIntegrationLUT, IBLIntegrationSampler, NoV, alpha); + Rs = prefilteredColor * (F * envBRDF.x + envBRDF.y); + } + float3 Rs_dieletric = (specular * dielectric * Rs); + float3 Rs_metallic = (metallic * Rs); + float3 BRDF = Kd * Rd + (Rs_dieletric + Rs_metallic); + + indirectLighting = BRDF; + } + + // Final color + float3 finalColor = indirectLighting; + finalColor = pow(finalColor, 1 / 2.2); + + // Output + return float4(finalColor, 1); +} diff --git a/projects/common/mtl_faux_render.cpp b/projects/common/mtl_faux_render.cpp index 6adda295..a3f73259 100644 --- a/projects/common/mtl_faux_render.cpp +++ b/projects/common/mtl_faux_render.cpp @@ -328,78 +328,6 @@ MtlFauxRender::Image* Cast(FauxRender::Image* pImage) return static_cast(pImage); } -bool CalculateVertexStrides(FauxRender::Scene* pScene, std::vector& vertexStrides) -{ - assert((pScene != nullptr) && "pScene is NULL"); - - bool meshPartStrideMismatch = false; - - vertexStrides.resize(4); - - for (auto pGeometryNode : pScene->GeometryNodes) - { - assert((pGeometryNode != nullptr) && "pGeometryNode is NULL"); - assert((pGeometryNode->Type == FauxRender::SCENE_NODE_TYPE_GEOMETRY) && "node is not of drawable type"); - - const FauxRender::Mesh* pMesh = pGeometryNode->pMesh; - - for (auto& batch : pMesh->DrawBatches) - { - // Position - { - assert((batch.PositionBufferView.Format != GREX_FORMAT_UNKNOWN) && "Mesh does not contain positions!"); - uint32_t vertexStride = batch.PositionBufferView.Stride; - - meshPartStrideMismatch = meshPartStrideMismatch || - (vertexStrides[kPositionIndex] != vertexStride && vertexStrides[kPositionIndex] != 0); - - vertexStrides[kPositionIndex] = vertexStride; - } - - // Tex Coord - { - uint32_t texCoordStride = batch.NormalBufferView.Format != GREX_FORMAT_UNKNOWN - ? batch.TexCoordBufferView.Stride - : batch.PositionBufferView.Stride; // Can't have zero stride - - meshPartStrideMismatch = meshPartStrideMismatch || - (vertexStrides[kTexCoordIndex] != texCoordStride && vertexStrides[kTexCoordIndex] != 0); - - vertexStrides[kTexCoordIndex] = texCoordStride; - } - - // Normal - { - uint32_t normalStride = batch.NormalBufferView.Format != GREX_FORMAT_UNKNOWN - ? batch.NormalBufferView.Stride - : batch.PositionBufferView.Stride; // Can't have zero stride - - meshPartStrideMismatch = meshPartStrideMismatch || - (vertexStrides[kNormalIndex] != normalStride && vertexStrides[kNormalIndex] != 0); - - vertexStrides[kNormalIndex] = normalStride; - } - - // Tangent - { - uint32_t tangentStride = batch.TangentBufferView.Format != GREX_FORMAT_UNKNOWN - ? batch.TangentBufferView.Stride - : batch.PositionBufferView.Stride; // Can't have zero stride - - meshPartStrideMismatch = meshPartStrideMismatch || - (vertexStrides[kTangentIndex] != tangentStride && vertexStrides[kTangentIndex] != 0); - - vertexStrides[kTangentIndex] = tangentStride; - } - } - } - - if (meshPartStrideMismatch) - { - assert(false && "Not all mesh parts use the same vertex strides, which is not supported on Metal"); - } -} - void Draw(const FauxRender::SceneGraph* pGraph, uint32_t instanceIndex, const FauxRender::Mesh* pMesh, MTL::RenderCommandEncoder* pRenderEncoder) { assert((pMesh != nullptr) && "pMesh is NULL"); @@ -413,7 +341,7 @@ void Draw(const FauxRender::SceneGraph* pGraph, uint32_t instanceIndex, const Fa auto& batch = pMesh->DrawBatches[batchIdx]; // Skip if no material - if (IsNull(batch.pMaterial)) + if (IsNull(batch.pMaterial) || (batch.PositionBufferView.Stride != 48)) { continue; } @@ -425,14 +353,14 @@ void Draw(const FauxRender::SceneGraph* pGraph, uint32_t instanceIndex, const Fa // Position { - assert(batch.PositionBufferView.Format != GREX_FORMAT_UNKNOWN); - bufferViews[kPositionIndex] = pBuffer->Resource.Buffer.get(); - bufferOffsets[kPositionIndex] = batch.PositionBufferView.Offset; + assert(batch.PositionBufferView.Format != GREX_FORMAT_UNKNOWN); + bufferViews[kPositionIndex] = pBuffer->Resource.Buffer.get(); + bufferOffsets[kPositionIndex] = batch.PositionBufferView.Offset; } // Tex Coord { - bufferViews[kTexCoordIndex] = pBuffer->Resource.Buffer.get(); + bufferViews[kTexCoordIndex] = pBuffer->Resource.Buffer.get(); bufferOffsets[kTexCoordIndex] = batch.TexCoordBufferView.Format != GREX_FORMAT_UNKNOWN ? batch.TexCoordBufferView.Offset @@ -441,7 +369,7 @@ void Draw(const FauxRender::SceneGraph* pGraph, uint32_t instanceIndex, const Fa // Normal { - bufferViews[kNormalIndex] = pBuffer->Resource.Buffer.get(); + bufferViews[kNormalIndex] = pBuffer->Resource.Buffer.get(); bufferOffsets[kNormalIndex] = batch.NormalBufferView.Format != GREX_FORMAT_UNKNOWN ? batch.NormalBufferView.Offset @@ -450,7 +378,8 @@ void Draw(const FauxRender::SceneGraph* pGraph, uint32_t instanceIndex, const Fa // Tangent { - bufferViews[kTangentIndex] = pBuffer->Resource.Buffer.get(); + bufferViews[kTangentIndex] = pBuffer->Resource.Buffer.get(); + bufferOffsets[kTangentIndex] = batch.TangentBufferView.Format != GREX_FORMAT_UNKNOWN ? batch.TangentBufferView.Offset : batch.PositionBufferView.Offset; diff --git a/projects/common/mtl_renderer.cpp b/projects/common/mtl_renderer.cpp index afbf971c..7e90c22c 100644 --- a/projects/common/mtl_renderer.cpp +++ b/projects/common/mtl_renderer.cpp @@ -992,8 +992,7 @@ NS::Error* CreateGraphicsPipeline2( MTL::PixelFormat rtvFormat, MTL::PixelFormat dsvFormat, MetalPipelineRenderState* pPipelineRenderState, - MetalDepthStencilState* pDepthStencilState, - uint32_t* pOptionalVertexStrides) + MetalDepthStencilState* pDepthStencilState) { MTL::VertexDescriptor* pVertexDescriptor = MTL::VertexDescriptor::alloc()->init(); if (pVertexDescriptor != nullptr) diff --git a/projects/common/mtl_renderer.h b/projects/common/mtl_renderer.h index a50d6ed3..e1cbe530 100644 --- a/projects/common/mtl_renderer.h +++ b/projects/common/mtl_renderer.h @@ -151,5 +151,4 @@ NS::Error* CreateGraphicsPipeline2( MTL::PixelFormat rtvFormat, MTL::PixelFormat dsvFormat, MetalPipelineRenderState* pPipeline, - MetalDepthStencilState* pDepthStencilState, - uint32_t* pOptionalStrides); + MetalDepthStencilState* pDepthStencilState); diff --git a/projects/io/405_gltf_full_material_test_metal/405_gltf_full_material_test_metal.cpp b/projects/io/405_gltf_full_material_test_metal/405_gltf_full_material_test_metal.cpp new file mode 100644 index 00000000..6fd16204 --- /dev/null +++ b/projects/io/405_gltf_full_material_test_metal/405_gltf_full_material_test_metal.cpp @@ -0,0 +1,660 @@ +#include "window.h" + +#include "mtl_renderer.h" + +#include "mtl_faux_render.h" + +#include +#include +#include +#include +using namespace glm; + +#define CHECK_CALL(FN) \ + { \ + NS::Error* pError = FN; \ + if (pError != nullptr) \ + { \ + std::stringstream ss; \ + ss << "\n"; \ + ss << "*** FUNCTION CALL FAILED *** \n"; \ + ss << "FUNCTION: " << #FN << "\n"; \ + ss << "Error: " << pError->localizedDescription()->utf8String() << "\n"; \ + ss << "\n"; \ + GREX_LOG_ERROR(ss.str().c_str()); \ + assert(false); \ + } \ + } + +#define MAX_INSTANCES 100 +#define MAX_MATERIALS 100 +#define MAX_MATERIAL_SAMPLERS 32 +#define MAX_MATERIAL_IMAGES 1024 +#define MAX_IBL_TEXTURES 1 + +#define SCENE_REGISTER 4 +#define CAMERA_REGISTER 5 +#define DRAW_REGISTER 6 +#define INSTANCE_BUFFER_REGISTER 7 +#define MATERIAL_BUFFER_REGISTER 8 +#define MATERIAL_SAMPLER_START_REGISTER 9 +#define MATERIAL_IMAGES_START_REGISTER 10 +#define IBL_ENV_MAP_TEXTURE_START_REGISTER 11 +#define IBL_IRR_MAP_TEXTURE_START_REGISTER 12 +#define IBL_INTEGRATION_LUT_REGISTER 13 +#define IBL_MAP_SAMPLER_REGISTER 14 +#define IBL_INTEGRATION_SAMPLER_REGISTER 15 + +// ============================================================================= +// Globals +// ============================================================================= +static uint32_t gWindowWidth = 1280; +static uint32_t gWindowHeight = 720; +static bool gEnableDebug = true; + +static const uint32_t gNumIBLLUTs = 2; +static const uint32_t gNumIBLTextures = 1; +static const uint32_t gNumIBLEnvTextures = gNumIBLTextures; +static const uint32_t gNumIBLIrrTextures = gNumIBLTextures; +static const uint32_t gIBLLUTsOffset = 0; +static const uint32_t gIBLEnvTextureOffset = gIBLLUTsOffset + gNumIBLLUTs; +static const uint32_t gIBLIrrTextureOffset = gIBLEnvTextureOffset + gNumIBLEnvTextures; +static const uint32_t gMaterialTextureOffset = gIBLIrrTextureOffset + gNumIBLIrrTextures; + +static std::vector gIBLNames = {}; + +static float gTargetAngle = 0.0f; +static float gAngle = 0.0f; + +void CreateIBLTextures( + MetalRenderer* pRenderer, + MetalTexture* ppBRDFLUT, + MetalTexture* ppMultiscatterBRDFLUT, + std::vector& outIrradianceTextures, + std::vector& outEnvironmentTextures, + std::vector& outEnvNumLevels); + +void MouseMove(int x, int y, int buttons) +{ + static int prevX = x; + static int prevY = y; + + if (buttons & MOUSE_BUTTON_LEFT) + { + int dx = x - prevX; + int dy = y - prevY; + + gTargetAngle += 0.25f * dx; + } + + prevX = x; + prevY = y; +} + +// ============================================================================= +// main() +// ============================================================================= +int main(int argc, char** argv) +{ + std::unique_ptr renderer = std::make_unique(); + + if (!InitMetal(renderer.get(), gEnableDebug)) + { + return EXIT_FAILURE; + } + + // ************************************************************************* + // Compile shaders + // ************************************************************************* + std::string shaderSource = LoadString("faux_render_shaders/render_pbr_material.metal"); + + MetalShader vsShader; + MetalShader fsShader; + NS::Error* pError = nullptr; + auto library = NS::TransferPtr(renderer->Device->newLibrary( + NS::String::string(shaderSource.c_str(), NS::UTF8StringEncoding), + nullptr, + &pError)); + + if (library.get() == nullptr) + { + std::stringstream ss; + ss << "\n" + << "Shader compiler error (VS): " << pError->localizedDescription()->utf8String() << "\n"; + GREX_LOG_ERROR(ss.str().c_str()); + assert(false); + return EXIT_FAILURE; + } + + vsShader.Function = NS::TransferPtr(library->newFunction(NS::String::string("vsmain", NS::UTF8StringEncoding))); + if (vsShader.Function.get() == nullptr) + { + assert(false && "VS Shader MTL::Library::newFunction() failed"); + return EXIT_FAILURE; + } + + fsShader.Function = NS::TransferPtr(library->newFunction(NS::String::string("psmain", NS::UTF8StringEncoding))); + if (fsShader.Function.get() == nullptr) + { + assert(false && "FS Shader MTL::Library::newFunction() failed"); + return EXIT_FAILURE; + } + + // ************************************************************************* + // Scene + // ************************************************************************* + MtlFauxRender::SceneGraph graph = MtlFauxRender::SceneGraph(renderer.get()); + if (!FauxRender::LoadGLTF(GetAssetPath("scenes/material_test_001_ktx2/material_test_001.gltf"), {}, &graph)) + { + assert(false && "LoadGLTF failed"); + return EXIT_FAILURE; + } + if (!graph.InitializeResources()) + { + assert(false && "Graph resources initialization failed"); + return EXIT_FAILURE; + } + + graph.RootParameterIndices.Scene = SCENE_REGISTER; + graph.RootParameterIndices.Camera = CAMERA_REGISTER; + graph.RootParameterIndices.Draw = DRAW_REGISTER; + graph.RootParameterIndices.InstanceBuffer = INSTANCE_BUFFER_REGISTER; + graph.RootParameterIndices.MaterialBuffer = MATERIAL_BUFFER_REGISTER; + + // ************************************************************************* + // Graphics pipeline state object + // ************************************************************************* + MetalPipelineRenderState renderPipelineState; + MetalDepthStencilState depthStencilState; + CHECK_CALL(CreateGraphicsPipeline2( + renderer.get(), + &vsShader, + &fsShader, + GREX_DEFAULT_RTV_FORMAT, + GREX_DEFAULT_DSV_FORMAT, + &renderPipelineState, + &depthStencilState)); + + + // ************************************************************************* + // IBL textures + // ************************************************************************* + MetalTexture brdfLUT; + MetalTexture multiscatterBRDFLUT; + std::vector irrTextures; + std::vector envTextures; + std::vector envNumLevels; + CreateIBLTextures( + renderer.get(), + &brdfLUT, + &multiscatterBRDFLUT, + irrTextures, + envTextures, + envNumLevels); + + // ************************************************************************* + // ArgBuffers + // ************************************************************************* + MTL::Buffer* pMaterialImagesArgBuffer = nullptr; + MTL::Buffer* pMaterialSamplersArgBuffer = nullptr; + MTL::Buffer* pIrrImagesArgBuffer = nullptr; + MTL::Buffer* pEnvImagesArgBuffer = nullptr; + MTL::SamplerState* pIBLMapSamplerState = nullptr; + MTL::SamplerState* pIBLIntegrationSamplerState = nullptr; + std::vector materialImagesTextures; + std::vector materialSamplerStates; + std::vector irrImagesTextures; + std::vector envImagesTextures; + { + // Material Textures + { + MTL::ArgumentEncoder* pMaterialImagesArgEncoder = fsShader.Function->newArgumentEncoder(MATERIAL_IMAGES_START_REGISTER); + pMaterialImagesArgBuffer = renderer->Device->newBuffer( + pMaterialImagesArgEncoder->encodedLength(), + MTL::ResourceStorageModeManaged); + + pMaterialImagesArgEncoder->setArgumentBuffer(pMaterialImagesArgBuffer, 0); + + for (size_t i = 0; i < graph.Images.size(); ++i) + { + auto image = MtlFauxRender::Cast(graph.Images[i].get()); + MTL::Texture* resource = image->Resource.Texture.get(); + + pMaterialImagesArgEncoder->setTexture(resource, i); + materialImagesTextures.push_back(resource); + } + + pMaterialImagesArgBuffer->didModifyRange(NS::Range::Make(0, graph.Images.size())); + + pMaterialImagesArgEncoder->release(); + } + + // Material Samplers + { + uint32_t samplerCount = 0; + MTL::ArgumentEncoder* pMaterialSamplersArgEncoder = fsShader.Function->newArgumentEncoder(MATERIAL_SAMPLER_START_REGISTER); + pMaterialSamplersArgBuffer = renderer->Device->newBuffer( + pMaterialSamplersArgEncoder->encodedLength(), + MTL::ResourceStorageModeManaged); + + pMaterialSamplersArgEncoder->setArgumentBuffer(pMaterialSamplersArgBuffer, 0); + + // Clamped + { + MTL::SamplerDescriptor* clampedSamplerDesc = MTL::SamplerDescriptor::alloc()->init(); + + if (clampedSamplerDesc != nullptr) + { + clampedSamplerDesc->setSupportArgumentBuffers(true); + clampedSamplerDesc->setMinFilter(MTL::SamplerMinMagFilterLinear); + clampedSamplerDesc->setMagFilter(MTL::SamplerMinMagFilterLinear); + clampedSamplerDesc->setMipFilter(MTL::SamplerMipFilterLinear); + clampedSamplerDesc->setRAddressMode(MTL::SamplerAddressModeClampToEdge); + clampedSamplerDesc->setSAddressMode(MTL::SamplerAddressModeClampToEdge); + clampedSamplerDesc->setTAddressMode(MTL::SamplerAddressModeClampToEdge); + clampedSamplerDesc->setBorderColor(MTL::SamplerBorderColorOpaqueBlack); + + MTL::SamplerState* clampedSampler = renderer->Device->newSamplerState(clampedSamplerDesc); + pMaterialSamplersArgEncoder->setSamplerState(clampedSampler, samplerCount); + samplerCount++; + + clampedSamplerDesc->release(); + } + else + { + assert(false && "Clamped sampler creation failure"); + return EXIT_FAILURE; + } + } + + // Repeat + { + MTL::SamplerDescriptor* repeatSamplerDesc = MTL::SamplerDescriptor::alloc()->init(); + + if (repeatSamplerDesc != nullptr) + { + repeatSamplerDesc->setSupportArgumentBuffers(true); + repeatSamplerDesc->setMinFilter(MTL::SamplerMinMagFilterLinear); + repeatSamplerDesc->setMagFilter(MTL::SamplerMinMagFilterLinear); + repeatSamplerDesc->setMipFilter(MTL::SamplerMipFilterLinear); + repeatSamplerDesc->setRAddressMode(MTL::SamplerAddressModeRepeat); + repeatSamplerDesc->setSAddressMode(MTL::SamplerAddressModeRepeat); + repeatSamplerDesc->setTAddressMode(MTL::SamplerAddressModeRepeat); + repeatSamplerDesc->setBorderColor(MTL::SamplerBorderColorOpaqueBlack); + + MTL::SamplerState* repeatSampler = renderer->Device->newSamplerState(repeatSamplerDesc); + pMaterialSamplersArgEncoder->setSamplerState(repeatSampler, samplerCount); + samplerCount++; + + repeatSamplerDesc->release(); + } + else + { + assert(false && "Repeat sampler creation failure"); + return EXIT_FAILURE; + } + + pMaterialSamplersArgBuffer->didModifyRange(NS::Range::Make(0, samplerCount)); + } + + pMaterialSamplersArgEncoder->release(); + } + + // IBL Textures + { + // Irradiance Textures + { + MTL::ArgumentEncoder* pIrrImagesArgEncoder = + fsShader.Function->newArgumentEncoder(IBL_IRR_MAP_TEXTURE_START_REGISTER); + + pIrrImagesArgBuffer = renderer->Device->newBuffer( + pIrrImagesArgEncoder->encodedLength(), + MTL::ResourceStorageModeManaged); + + pIrrImagesArgEncoder->setArgumentBuffer(pIrrImagesArgBuffer, 0); + + for (size_t i = 0; i < irrTextures.size(); ++i) + { + MTL::Texture* irrTexture = irrTextures[i].Texture.get(); + pIrrImagesArgEncoder->setTexture(irrTexture, i); + irrImagesTextures.push_back(irrTexture); + } + + pIrrImagesArgBuffer->didModifyRange(NS::Range::Make(0, irrImagesTextures.size())); + pIrrImagesArgEncoder->release(); + } + + // Environment textures + { + MTL::ArgumentEncoder* pEnvImagesArgEncoder = + fsShader.Function->newArgumentEncoder(IBL_IRR_MAP_TEXTURE_START_REGISTER); + + pEnvImagesArgBuffer = renderer->Device->newBuffer( + pEnvImagesArgEncoder->encodedLength(), + MTL::ResourceStorageModeManaged); + + pEnvImagesArgEncoder->setArgumentBuffer(pEnvImagesArgBuffer, 0); + + for (size_t i = 0; i < envTextures.size(); ++i) + { + MTL::Texture* envTexture = envTextures[i].Texture.get(); + pEnvImagesArgEncoder->setTexture(envTexture, i); + envImagesTextures.push_back(envTexture); + } + + pEnvImagesArgBuffer->didModifyRange(NS::Range::Make(0, envImagesTextures.size())); + pEnvImagesArgEncoder->release(); + } + } + + // IBL Samplers + { + uint32_t samplerCount = 0; + + // IBL Map Sampler + { + MTL::SamplerDescriptor* iblMapSamplerDesc = MTL::SamplerDescriptor::alloc()->init(); + + if (iblMapSamplerDesc != nullptr) + { + iblMapSamplerDesc->setSupportArgumentBuffers(true); + iblMapSamplerDesc->setMinFilter(MTL::SamplerMinMagFilterLinear); + iblMapSamplerDesc->setMagFilter(MTL::SamplerMinMagFilterLinear); + iblMapSamplerDesc->setMipFilter(MTL::SamplerMipFilterLinear); + iblMapSamplerDesc->setRAddressMode(MTL::SamplerAddressModeClampToEdge); + iblMapSamplerDesc->setSAddressMode(MTL::SamplerAddressModeClampToEdge); + iblMapSamplerDesc->setTAddressMode(MTL::SamplerAddressModeClampToEdge); + iblMapSamplerDesc->setBorderColor(MTL::SamplerBorderColorOpaqueBlack); + + pIBLMapSamplerState = renderer->Device->newSamplerState(iblMapSamplerDesc); + samplerCount++; + + iblMapSamplerDesc->release(); + } + else + { + assert(false && "IBL Map sampler creation failure"); + return EXIT_FAILURE; + } + } + + // Repeat + { + MTL::SamplerDescriptor* iblIntegrationSamplerDesc = MTL::SamplerDescriptor::alloc()->init(); + + if (iblIntegrationSamplerDesc != nullptr) + { + iblIntegrationSamplerDesc->setSupportArgumentBuffers(true); + iblIntegrationSamplerDesc->setMinFilter(MTL::SamplerMinMagFilterLinear); + iblIntegrationSamplerDesc->setMagFilter(MTL::SamplerMinMagFilterLinear); + iblIntegrationSamplerDesc->setMipFilter(MTL::SamplerMipFilterLinear); + iblIntegrationSamplerDesc->setRAddressMode(MTL::SamplerAddressModeRepeat); + iblIntegrationSamplerDesc->setSAddressMode(MTL::SamplerAddressModeRepeat); + iblIntegrationSamplerDesc->setTAddressMode(MTL::SamplerAddressModeRepeat); + iblIntegrationSamplerDesc->setBorderColor(MTL::SamplerBorderColorOpaqueBlack); + + pIBLIntegrationSamplerState = renderer->Device->newSamplerState(iblIntegrationSamplerDesc); + samplerCount++; + + iblIntegrationSamplerDesc->release(); + } + else + { + assert(false && "Repeat sampler creation failure"); + return EXIT_FAILURE; + } + } + } + } + + // ************************************************************************* + // Window + // ************************************************************************* + auto window = Window::Create(gWindowWidth, gWindowHeight, "405_gltf_full_material_test_case_metal"); + if (!window) + { + assert(false && "Window::Create failed"); + return EXIT_FAILURE; + } + window->AddMouseMoveCallbacks(MouseMove); + + // ************************************************************************* + // Render Pass Description + // ************************************************************************* + MTL::RenderPassDescriptor* pRenderPassDescriptor = MTL::RenderPassDescriptor::renderPassDescriptor(); + + // ************************************************************************* + // Swapchain + // ************************************************************************* + if (!InitSwapchain(renderer.get(), window->GetNativeWindow(), window->GetWidth(), window->GetHeight(), 2, GREX_DEFAULT_DSV_FORMAT)) + { + assert(false && "InitSwapchain failed"); + return EXIT_FAILURE; + } + + // ************************************************************************* + // Main loop + // ************************************************************************* + MTL::ClearColor clearColor(0.23f, 0.23f, 0.31f, 0); + uint32_t frameIndex = 0; + + while (window->PollEvents()) + { + CA::MetalDrawable* pDrawable = renderer->pSwapchain->nextDrawable(); + assert(pDrawable != nullptr); + + uint32_t swapchainIndex = (frameIndex % renderer->SwapchainBufferCount); + frameIndex++; + + auto colorTargetDesc = NS::TransferPtr(MTL::RenderPassColorAttachmentDescriptor::alloc()->init()); + colorTargetDesc->setClearColor(clearColor); + colorTargetDesc->setTexture(pDrawable->texture()); + colorTargetDesc->setLoadAction(MTL::LoadActionClear); + colorTargetDesc->setStoreAction(MTL::StoreActionStore); + pRenderPassDescriptor->colorAttachments()->setObject(colorTargetDesc.get(), 0); + + auto depthTargetDesc = NS::TransferPtr(MTL::RenderPassDepthAttachmentDescriptor::alloc()->init()); + depthTargetDesc->setClearDepth(1); + depthTargetDesc->setTexture(renderer->SwapchainDSVBuffers[swapchainIndex].get()); + depthTargetDesc->setLoadAction(MTL::LoadActionClear); + depthTargetDesc->setStoreAction(MTL::StoreActionDontCare); + pRenderPassDescriptor->setDepthAttachment(depthTargetDesc.get()); + + MTL::CommandBuffer* pCommandBuffer = renderer->Queue->commandBuffer(); + MTL::RenderCommandEncoder* pRenderEncoder = pCommandBuffer->renderCommandEncoder(pRenderPassDescriptor); + + MTL::Viewport viewport = {0, 0, static_cast(gWindowWidth), static_cast(gWindowHeight), 0, 1}; + pRenderEncoder->setViewport(viewport); + + MTL::ScissorRect scissor = {0, 0, gWindowWidth, gWindowHeight}; + pRenderEncoder->setScissorRect(scissor); + + pRenderEncoder->setRenderPipelineState(renderPipelineState.State.get()); + pRenderEncoder->setDepthStencilState(depthStencilState.State.get()); + + pRenderEncoder->setVertexBytes(&envNumLevels[0], 16, SCENE_REGISTER); + pRenderEncoder->setFragmentBytes(&envNumLevels[0], 16, SCENE_REGISTER); + + for (size_t i = 0; i < materialImagesTextures.size(); ++i) + { + pRenderEncoder->useResource(materialImagesTextures[i], MTL::ResourceUsageRead); + } + + pRenderEncoder->setVertexBuffer(pMaterialImagesArgBuffer, 0, MATERIAL_IMAGES_START_REGISTER); + pRenderEncoder->setFragmentBuffer(pMaterialImagesArgBuffer, 0, MATERIAL_IMAGES_START_REGISTER); + + pRenderEncoder->setVertexBuffer(pMaterialSamplersArgBuffer, 0, MATERIAL_SAMPLER_START_REGISTER); + pRenderEncoder->setFragmentBuffer(pMaterialSamplersArgBuffer, 0, MATERIAL_SAMPLER_START_REGISTER); + + pRenderEncoder->setVertexTexture(brdfLUT.Texture.get(), IBL_INTEGRATION_LUT_REGISTER); + pRenderEncoder->setFragmentTexture(brdfLUT.Texture.get(), IBL_INTEGRATION_LUT_REGISTER); + + for (size_t i = 0; i < irrImagesTextures.size(); ++i) + { + pRenderEncoder->useResource(irrImagesTextures[i], MTL::ResourceUsageRead); + } + + pRenderEncoder->setVertexBuffer(pIrrImagesArgBuffer, 0, IBL_IRR_MAP_TEXTURE_START_REGISTER); + pRenderEncoder->setFragmentBuffer(pIrrImagesArgBuffer, 0, IBL_IRR_MAP_TEXTURE_START_REGISTER); + + for (size_t i = 0; i < envImagesTextures.size(); ++i) + { + pRenderEncoder->useResource(envImagesTextures[i], MTL::ResourceUsageRead); + } + + pRenderEncoder->setVertexBuffer(pEnvImagesArgBuffer, 0, IBL_ENV_MAP_TEXTURE_START_REGISTER); + pRenderEncoder->setFragmentBuffer(pEnvImagesArgBuffer, 0, IBL_ENV_MAP_TEXTURE_START_REGISTER); + + pRenderEncoder->setVertexSamplerState(pIBLMapSamplerState, IBL_MAP_SAMPLER_REGISTER); + pRenderEncoder->setFragmentSamplerState(pIBLMapSamplerState, IBL_MAP_SAMPLER_REGISTER); + + pRenderEncoder->setVertexSamplerState(pIBLIntegrationSamplerState, IBL_INTEGRATION_SAMPLER_REGISTER); + pRenderEncoder->setFragmentSamplerState(pIBLIntegrationSamplerState, IBL_INTEGRATION_SAMPLER_REGISTER); + + // Draw scene + const auto& scene = graph.Scenes[0]; + MtlFauxRender::Draw(&graph, scene.get(), pRenderEncoder); + + pRenderEncoder->endEncoding(); + + pCommandBuffer->presentDrawable(pDrawable); + pCommandBuffer->commit(); + } + + return 0; +} + +void CreateIBLTextures( + MetalRenderer* pRenderer, + MetalTexture* ppBRDFLUT, + MetalTexture* ppMultiscatterBRDFLUT, + std::vector& outIrradianceTextures, + std::vector& outEnvironmentTextures, + std::vector& outEnvNumLevels) +{ + // BRDF LUT + { + auto bitmap = LoadImage32f(GetAssetPath("IBL/brdf_lut.hdr")); + if (bitmap.Empty()) + { + assert(false && "Load image failed"); + return; + } + + CHECK_CALL(CreateTexture( + pRenderer, + bitmap.GetWidth(), + bitmap.GetHeight(), + MTL::PixelFormatRGBA32Float, + bitmap.GetSizeInBytes(), + bitmap.GetPixels(), + ppBRDFLUT)); + } + + // Multiscatter BRDF LUT + { + auto bitmap = LoadImage32f(GetAssetPath("IBL/brdf_lut_ms.hdr")); + if (bitmap.Empty()) + { + assert(false && "Load image failed"); + return; + } + + CHECK_CALL(CreateTexture( + pRenderer, + bitmap.GetWidth(), + bitmap.GetHeight(), + MTL::PixelFormatRGBA32Float, + bitmap.GetSizeInBytes(), + bitmap.GetPixels(), + ppMultiscatterBRDFLUT)); + } + + // auto iblDir = GetAssetPath("IBL"); + // std::vector iblFiles; + // for (auto& entry : std::filesystem::directory_iterator(iblDir)) + //{ + // if (!entry.is_regular_file()) + // { + // continue; + // } + // auto path = entry.path(); + // auto ext = path.extension(); + // if (ext == ".ibl") + // { + // path = std::filesystem::relative(path, iblDir.parent_path()); + // iblFiles.push_back(path); + // } + // } + + std::vector iblFiles = {GetAssetPath("IBL/machine_shop_01_4k.ibl")}; + + size_t maxEntries = std::min(gNumIBLTextures, iblFiles.size()); + for (size_t i = 0; i < maxEntries; ++i) + { + std::filesystem::path iblFile = iblFiles[i]; + + IBLMaps ibl = {}; + if (!LoadIBLMaps32f(iblFile, &ibl)) + { + GREX_LOG_ERROR("failed to load: " << iblFile); + assert(false && "IBL maps load failed failed"); + return; + } + + outEnvNumLevels.push_back(ibl.numLevels); + + // Irradiance + { + MetalTexture texture; + CHECK_CALL(CreateTexture( + pRenderer, + ibl.irradianceMap.GetWidth(), + ibl.irradianceMap.GetHeight(), + MTL::PixelFormatRGBA32Float, + ibl.irradianceMap.GetSizeInBytes(), + ibl.irradianceMap.GetPixels(), + &texture)); + outIrradianceTextures.push_back(texture); + } + + // Environment + { + const uint32_t pixelStride = ibl.environmentMap.GetPixelStride(); + const uint32_t rowStride = ibl.environmentMap.GetRowStride(); + + std::vector mipOffsets; + uint32_t levelOffset = 0; + uint32_t levelWidth = ibl.baseWidth; + uint32_t levelHeight = ibl.baseHeight; + for (uint32_t i = 0; i < ibl.numLevels; ++i) + { + MipOffset mipOffset = {}; + mipOffset.Offset = levelOffset; + mipOffset.RowStride = rowStride; + + mipOffsets.push_back(mipOffset); + + levelOffset += (rowStride * levelHeight); + levelWidth >>= 1; + levelHeight >>= 1; + } + + MetalTexture texture; + CHECK_CALL(CreateTexture( + pRenderer, + ibl.baseWidth, + ibl.baseHeight, + MTL::PixelFormatRGBA32Float, + mipOffsets, + ibl.environmentMap.GetSizeInBytes(), + ibl.environmentMap.GetPixels(), + &texture)); + outEnvironmentTextures.push_back(texture); + } + + gIBLNames.push_back(iblFile.filename().replace_extension().string()); + + GREX_LOG_INFO("Loaded " << iblFile); + } +} diff --git a/projects/io/405_gltf_full_material_test_metal/CMakeLists.txt b/projects/io/405_gltf_full_material_test_metal/CMakeLists.txt new file mode 100644 index 00000000..f4b590d7 --- /dev/null +++ b/projects/io/405_gltf_full_material_test_metal/CMakeLists.txt @@ -0,0 +1,52 @@ +cmake_minimum_required(VERSION 3.0) + +project(405_gltf_full_material_test_metal) + +add_executable( + 405_gltf_full_material_test_metal + 405_gltf_full_material_test_metal.cpp + ${GREX_PROJECTS_COMMON_DIR}/config.h + ${GREX_PROJECTS_COMMON_DIR}/mtl_renderer.h + ${GREX_PROJECTS_COMMON_DIR}/mtl_renderer.cpp + ${GREX_PROJECTS_COMMON_DIR}/mtl_renderer_utils.h + ${GREX_PROJECTS_COMMON_DIR}/mtl_renderer_utils.mm + ${GREX_PROJECTS_COMMON_DIR}/window.h + ${GREX_PROJECTS_COMMON_DIR}/window.cpp + ${GREX_PROJECTS_COMMON_DIR}/cgltf_impl.cpp + ${GREX_THIRD_PARTY_DIR}/cgltf/cgltf.h + ${GREX_PROJECTS_COMMON_DIR}/faux_render.h + ${GREX_PROJECTS_COMMON_DIR}/faux_render.cpp + ${GREX_PROJECTS_COMMON_DIR}/mtl_faux_render.h + ${GREX_PROJECTS_COMMON_DIR}/mtl_faux_render.cpp + ${GREX_PROJECTS_COMMON_DIR}/bitmap.h + ${GREX_PROJECTS_COMMON_DIR}/bitmap.cpp + ${GREX_THIRD_PARTY_DIR}/glslang/StandAlone/resource_limits_c.cpp + ${GREX_THIRD_PARTY_DIR}/glslang/StandAlone/ResourceLimits.cpp +) + +set_target_properties(405_gltf_full_material_test_metal PROPERTIES FOLDER "io") + +target_include_directories( + 405_gltf_full_material_test_metal + PUBLIC ${GREX_PROJECTS_COMMON_DIR} + ${GREX_THIRD_PARTY_DIR}/glm + ${GREX_THIRD_PARTY_DIR}/tinyobjloader + ${GREX_THIRD_PARTY_DIR}/cgltf + ${GREX_THIRD_PARTY_DIR}/stb + ${GREX_THIRD_PARTY_DIR}/MikkTSpace + ${GREX_THIRD_PARTY_DIR}/metal-cpp +) + +FIND_LIBRARY(FOUNDATION_LIBRARY Foundation) +FIND_LIBRARY(METAL_LIBRARY Metal) +FIND_LIBRARY(METALKIT_LIBRARY MetalKit) + +target_link_libraries( + 405_gltf_full_material_test_metal + PUBLIC glfw + glslang + ${FOUNDATION_LIBRARY} + ${METAL_LIBRARY} + ${METALKIT_LIBRARY} + ktx +) diff --git a/projects/io/CMakeLists.txt b/projects/io/CMakeLists.txt index 0fac3d31..19746576 100644 --- a/projects/io/CMakeLists.txt +++ b/projects/io/CMakeLists.txt @@ -24,4 +24,5 @@ if (GREX_ENABLE_METAL) add_subdirectory(402_gltf_basic_texture_metal) add_subdirectory(403_gltf_basic_material_metal) add_subdirectory(404_gltf_basic_material_texture_metal) + add_subdirectory(405_gltf_full_material_test_metal) endif()