'How to setup physical based rendering in SceneKit from code?
I want to present some scene with PBR. I created metalness and roughness textures and want to apply it to the mesh. When I try to do it in Xcode - everything is fine. I can add this model to the scene and then add this textures to the model.
But I want to do it programmatically. Here is my code:
NSData *vertices = [[NSData alloc] initWithBytes:modelPly->vertices length:modelPly->vertexCount * 3 * sizeof(float)];
SCNGeometrySource *vertexSource = [SCNGeometrySource geometrySourceWithData:vertices
semantic:SCNGeometrySourceSemanticVertex
vectorCount:modelPly->vertexCount
floatComponents:YES
componentsPerVector:3
bytesPerComponent:sizeof(float)
dataOffset:0
dataStride:sizeof(float) * 3];
// Normal source
NSData *normals = [[NSData alloc] initWithBytes:modelPly->normals length:modelPly->vertexCount * 3 * sizeof(float)];
SCNGeometrySource *normalSource = [SCNGeometrySource geometrySourceWithData:normals
semantic:SCNGeometrySourceSemanticNormal
vectorCount:modelPly->vertexCount
floatComponents:YES
componentsPerVector:3
bytesPerComponent:sizeof(float)
dataOffset:0
dataStride:sizeof(float) * 3];
// Texture coordinates source
NSData *facesTextures = [[NSData alloc] initWithBytes:modelPly->texCoord length:modelPly->vertexCount * 2 * sizeof(float)];
SCNGeometrySource *texcoordSource = [SCNGeometrySource geometrySourceWithData:facesTextures
semantic:SCNGeometrySourceSemanticTexcoord
vectorCount:modelPly->vertexCount
floatComponents:YES
componentsPerVector:2
bytesPerComponent:sizeof(float)
dataOffset:0
dataStride:sizeof(float) * 2];
NSData *indicesData = [NSData dataWithBytes:modelPly->indices length:modelPly->indexCount * sizeof(uint) * 3];
SCNGeometryElement *elements = [SCNGeometryElement geometryElementWithData:indicesData
primitiveType:SCNGeometryPrimitiveTypeTriangles
primitiveCount:modelPly->indexCount
bytesPerIndex:sizeof(uint)];
SCNGeometry *geometry = [SCNGeometry geometryWithSources:@[vertexSource, normalSource, texcoordSource] elements:@[elements]];
MDLMesh *mesh = [MDLMesh meshWithSCNGeometry:geometry];
Then I'm trying to create material:
MDLPhysicallyPlausibleScatteringFunction *scatFunction = [MDLPhysicallyPlausibleScatteringFunction new];
MDLMaterial *mdlMaterial = [[MDLMaterial alloc] initWithName:@"material" scatteringFunction:scatFunction];
[mdlMaterial removeAllProperties];
MDLMaterialProperty *bcProperty = [[MDLMaterialProperty alloc] initWithName:@"BaseColor" semantic:MDLMaterialSemanticBaseColor URL:plyItem.textureFileURL];
MDLMaterialProperty *metalnessProperty = [[MDLMaterialProperty alloc] initWithName:@"metallic" semantic:MDLMaterialSemanticMetallic URL:[[NSBundle mainBundle] URLForResource:@"metalness" withExtension:@"jpg"]];
MDLMaterialProperty *roughnessProperty = [[MDLMaterialProperty alloc] initWithName:@"roughness" semantic:MDLMaterialSemanticRoughness URL:[[NSBundle mainBundle] URLForResource:@"roughness" withExtension:@"jpg"]];
[mdlMaterial setProperty:bcProperty];
[mdlMaterial setProperty:metalnessProperty];
[mdlMaterial setProperty:roughnessProperty];
and apply it to my geometry:
SCNNode *node = [SCNNode nodeWithMDLObject:mesh];
SCNMaterial *material = [SCNMaterial materialWithMDLMaterial:mdlMaterial];
material.lightingModelName = SCNLightingModelPhysicallyBased;
node.geometry.firstMaterial = material;
If I check in debugger mdlMaterial contains all information about roughness and metalness, but material doesn't. What's wrong?
Solution 1:[1]
Managed to do it via SCNGeometry only.
SCNNode *node = [SCNNode nodeWithGeometry:geometry];
node.geometry.firstMaterial.diffuse.contents = plyItem.textureFileURL;
node.geometry.firstMaterial.metalness.contents = [[[NSBundle mainBundle] URLForResource:@"metalness" withExtension:@"jpg"] path];
node.geometry.firstMaterial.roughness.contents = [[[NSBundle mainBundle] URLForResource:@"roughness" withExtension:@"jpg"] path];
node.geometry.firstMaterial.lightingModelName = SCNLightingModelPhysicallyBased;
Solution 2:[2]
Assign it thru any material slot located in SCNGeometry:
// Swift 5.6
let sphereNode = SCNNode(geometry: SCNSphere(radius: 1))
let material = sphereNode.geometry?.firstMaterial
material?.roughness.contents = 0.12
material?.metalness.contents = 0.95
material?.lightingModel = .physicallyBased
// Objective-C
sphereNode.geometry.firstMaterial.roughness.contents = @0.12;
sphereNode.geometry.firstMaterial.metalness.contents = @0.95;
sphereNode.geometry.firstMaterial.lightingModelName = SCNLightingModelPhysicallyBased;
Here's a macOS code sample written in Objective-C for testing:
#import "GameViewController.h"
@implementation GameViewController
- (void)viewDidLoad
{
[super viewDidLoad];
SCNView *sceneView = (SCNView *)self.view;
SCNScene *scene = [SCNScene scene];
sceneView.scene = scene;
sceneView.allowsCameraControl = YES;
sceneView.autoenablesDefaultLighting = YES;
sceneView.backgroundColor = [NSColor blackColor];
SCNMaterial *material = [SCNMaterial material];
material.lightingModelName = SCNLightingModelPhysicallyBased;
material.diffuse.contents = [NSImage imageNamed:@"YourPicture"];
material.metalness.contents = [NSColor whiteColor];
material.roughness.contents = [NSColor blackColor];
SCNNode *sphere = [SCNNode node];
sphere.geometry = [SCNSphere sphereWithRadius:0.25];
sphere.geometry.firstMaterial = material;
[scene.rootNode addChildNode:sphere];
}
@end
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Joker |
Solution 2 |