首先,在继续之前,我已经阅读了:SceneKit 使用纹理坐标在纹理上绘制 https://stackoverflow.com/questions/26129111/ios8-scenekit-painting-on-texture-with-texture-coordinates这似乎表明我走在正确的轨道上。
我有一个复杂的 SCNGeometry 代表一个六球体。它的渲染效果非常好,在我的所有测试设备上都达到了 60 fps。
目前,所有六边形都使用单一材质渲染,因为据我了解,我添加到几何体中的每个 SCNMaterial 都会添加另一个绘制调用,这是我无法承受的。
最终,我希望能够为近 10,000 个六边形中的每一个单独着色,因此为每个六边形添加另一种材质是行不通的。
我一直计划将颜色范围限制为(比如说)100 种颜色,然后在不同的几何图形之间移动六边形,每个几何图形都有自己的颜色材质,但这行不通,因为 SCNGeometry 说它适用于一组不可变的顶点。
因此,我当前的想法/计划是使用 @rickster 在上述问题中建议的着色器修改器来以某种方式修改单个六边形(或 4 个三角形的集合)的颜色。
问题是,我有点理解所提到的Apple doco,但我不明白如何向着色器提供我认为本质上必须是颜色信息数组的内容,以某种方式进行索引,以便着色器知道哪些三角形要给出什么颜色。
我现在创建几何图形的代码如下:
NSData *indiceData = [NSData dataWithBytes:oneMeshIndices length:sizeof(UInt32) * indiceIndex];
SCNGeometryElement *oneMeshElement =
[SCNGeometryElement geometryElementWithData:indiceData
primitiveType:SCNGeometryPrimitiveTypeTriangles
primitiveCount:indiceIndex / 3
bytesPerIndex:sizeof(UInt32)];
[oneMeshElements addObject:oneMeshElement];
SCNGeometrySource *oneMeshNormalSource =
[SCNGeometrySource geometrySourceWithNormals:oneMeshNormals count:normalIndex];
SCNGeometrySource *oneMeshVerticeSource =
[SCNGeometrySource geometrySourceWithVertices:oneMeshVertices count:vertexIndex];
SCNGeometry *oneMeshGeom =
[SCNGeometry geometryWithSources:[NSArray arrayWithObjects:oneMeshVerticeSource, oneMeshNormalSource, nil]
elements:oneMeshElements];
SCNMaterial *mat1 = [SCNMaterial material];
mat1.diffuse.contents = [UIColor greenColor];
oneMeshGeom.materials = @[mat1];
SCNNode *node = [SCNNode nodeWithGeometry:oneMeshGeom];
如果有人可以阐明如何为着色器提供一种方法来为 indiceData 中的索引索引的每个三角形着色,那就太棒了。
EDIT
我尝试过为着色器提供纹理作为颜色信息的容器,该颜色信息将由 VertexID 索引,但似乎 SceneKit 并未使 VertexID 可用。我的想法是通过 SCNMaterialProperty 类提供此纹理(实际上只是一个字节数组,六球体上的每个六边形 1 个),然后在着色器中根据顶点编号提取适当的字节。该字节将用于索引固定颜色数组,然后每个顶点的最终颜色将给出所需的结果。
如果没有 VertexID,这个想法就行不通,除非有其他一些类似有用的数据......
EDIT 2
也许我很固执。我一直在努力让它发挥作用,作为一个实验,我创建了一个基本上是条纹彩虹的图像,并编写了以下着色器,认为它基本上可以用彩虹为我的球体着色。
这不起作用。整个球体是使用图像左上角的颜色绘制的。
我的shaderModifer代码是:
#pragma arguments
sampler2D colorMap;
uniform sampler2D colorMap;
#pragma body
vec4 color = texture2D(colorMap, _surface.diffuseTexcoord);
_surface.diffuse.rgba = color;
我使用以下代码应用它:
SCNMaterial *mat1 = [SCNMaterial material];
mat1.locksAmbientWithDiffuse = YES;
mat1.doubleSided = YES;
mat1.shaderModifiers = @{SCNShaderModifierEntryPointSurface :
@"#pragma arguments\nsampler2D colorMap;\nuniform sampler2D colorMap;\n#pragma body\nvec4 color = texture2D(colorMap, _surface.diffuseTexcoord);\n_surface.diffuse.rgba = color;"};
colorMap = [SCNMaterialProperty materialPropertyWithContents:[UIImage imageNamed:@"rainbow.png"]];
[mat1 setValue:colorMap forKeyPath:@"colorMap"];
我原以为 _surface.diffuseTexcoord 是合适的,但我开始认为我需要通过了解图像的尺寸并以某种方式进行插值,以某种方式将其映射到图像中的坐标。
但如果是这种情况,_surface.diffuseTexcoord 的单位是什么?我如何知道它的最小/最大范围,以便我可以将其映射到图像?
再次,我希望如果这些尝试是错误的,有人可以引导我走向正确的方向。
EDIT 3
好的,所以我知道我现在走在正确的轨道上。我已经意识到,通过使用 _surface.normal 而不是 _surface.diffuseTexcoord,我可以将其用作球体上的纬度/经度来映射到图像中的 x,y,现在我看到六边形根据中的颜色进行着色colorMap 但是我做什么并不重要(到目前为止);法线角度似乎相对于相机位置是固定的,因此当我移动相机以查看球体的不同点时,颜色贴图不会随之旋转。
这是最新的着色器代码:
#pragma arguments
sampler2D colorMap;
uniform sampler2D colorMap;
#pragma body
float x = ((_surface.normal.x * 57.29577951) + 180.0) / 360.0;
float y = 1.0 - ((_surface.normal.y * 57.29577951) + 90.0) / 180.0;
vec4 color = texture2D(colorMap, vec2(x, y));
_output.color.rgba = color;
ANSWER
所以我解决了这个问题。事实证明,不需要着色器就可以达到我想要的结果。
答案是使用mappingChannel http://developer.apple.com/reference/scenekit/scnmaterialproperty/1395405-mappingchannel为几何体的每个顶点提供一组纹理坐标。这些纹理坐标用于从适当的纹理中提取颜色数据(这完全取决于您如何设置材质)。
因此,虽然我确实设法让着色器正常工作,但旧设备上存在性能问题,而使用映射通道要好得多,现在在所有设备上都以 60fps 的速度工作。
我确实发现,尽管文档说映射通道是一系列 CGPoint 对象,但这在 64 位设备上不起作用,因为 CGPoint 似乎使用双精度而不是浮点数。
我需要定义我自己的结构:
typedef struct {
float x;
float y;
} MyPoint;
MyPoint oneMeshTextureCoordinates[vertexCount];
然后建立了一个数组,每个顶点一个,然后我创建了appingChannel源,如下所示:
SCNGeometrySource *textureMappingSource =
[SCNGeometrySource geometrySourceWithData:
[NSData dataWithBytes:oneMeshTextureCoordinates
length:sizeof(MyPoint) * vertexCount]
semantic:SCNGeometrySourceSemanticTexcoord
vertexCount
floatComponents:YES
componentsPerVector:2
bytesPerComponent:sizeof(float)
dataOffset:0
dataStride:sizeof(MyPoint)];
编辑:
为了响应请求,这里有一个项目演示了我如何使用它。https://github.com/pkclsoft/HexasphereDemo https://github.com/pkclsoft/HexasphereDemo