說明
學習ARKit前,需要先學習SceneKit,參考SceneKit系列文章目錄
更多iOS相關知識檢視github上WeekWeekUpProject
上一篇我們已經初步示範了:如何讀取著色器String,並通過shaderModifiers載入.
Shader型別SCNShaderModifierEntryPoint有geometry,surface,lightingModel,fragment.
使用示例
我們以surface型別為例.swift
如何給著色器傳參呢??直接使用KVC...
private func setupShader() {
guard let path = Bundle.main.path(forResource: "skin", ofType: "shaderModifier", inDirectory: "art.scnassets"),
let shader = try? String(contentsOfFile: path, encoding: String.Encoding.utf8) else {
return
}
skin.shaderModifiers = [SCNShaderModifierEntryPoint.surface: shader]
skin.setValue(Double(0), forKey: "blendFactor")
skin.setValue(NSValue(scnVector3: SCNVector3Zero), forKey: "skinColorFromEnvironment")
let sparseTexture = SCNMaterialProperty(contents: UIImage(named: "art.scnassets/textures/chameleon_DIFFUSE_BASE.png")!)
skin.setValue(sparseTexture, forKey: "sparseTexture")
}
複製程式碼
OC
NSURL *url = [[NSBundle mainBundle] URLForResource:@"skin" withExtension:@"shaderModifier"];
NSError *error;
NSString *shaderSource = [[NSString alloc] initWithContentsOfURL:url
encoding:NSUTF8StringEncoding
error:&error];
if (!shaderSource) {
// Handle the error
NSLog(@"Failed to load shader source code, with error: %@", [error localizedDescription]);
} else {
skin.shaderModifiers = @{ SCNShaderModifierEntryPointSurface : shader };
}
[skin setValue:@(0) forKey:@"blendFactor"];
[skin setValue:[NSValue valueWithSCNVector3:SCNVector3Zero] forKey:@"skinColorFromEnvironment"];
SCNMaterialProperty *sparseTexture = [SCNMaterialProperty materialPropertyWithContents:[NSImage imageNamed:@"art.scnassets/textures/chameleon_DIFFUSE_BASE.png"]];
[skin setValue:sparseTexture forKey:@"sparseTexture"];
複製程式碼
shader型別詳解
- SCNShaderModifierEntryPointGeometry : 用來處理幾何體形變.
輸入為結構體geometry:
struct SCNShaderGeometry {
float4 position;
float3 normal;
float4 tangent;
float4 color;
float2 texcoords[kSCNTexcoordCount];
} _geometry;
Access: ReadWrite 訪問許可權:讀寫
Stages: Vertex shader only 只限於頂點著色器中
複製程式碼
kSCNTexcoordCount
是個整型常量,表示用到的頂點座標數.
其中的向量及座標(position, normal and tangent)為模型空間.
著色器示例,正弦變形:
GLSL
uniform float Amplitude = 0.1;
_geometry.position.xyz += _geometry.normal * (Amplitude * _geometry.position.y * _geometry.position.x) * sin(u_time);
Metal Shading Language
#pragma arguments
float Amplitude;
#pragma body
_geometry.position.xyz += _geometry.normal * (Amplitude * _geometry.position.y * _geometry.position.x) * sin(scn_frame.time);
複製程式碼
- SCNShaderModifierEntryPointSurface : 用來在光照應用前處理材質.
輸入為結構體surface:
struct SCNShaderSurface {
float3 view; // Direction from the point on the surface toward the camera (V)
float3 position; // Position of the fragment
float3 normal; // Normal of the fragment (N)
float3 geometryNormal; // Geometric normal of the fragment (normal map is ignored)
float3 tangent; // Tangent of the fragment
float3 bitangent; // Bitangent of the fragment
float4 ambient; // Ambient property of the fragment
float2 ambientTexcoord; // Ambient texture coordinates
float4 diffuse; // Diffuse property of the fragment. Alpha contains the opacity.
float2 diffuseTexcoord; // Diffuse texture coordinates
float4 specular; // Specular property of the fragment
float2 specularTexcoord; // Specular texture coordinates
float4 emission; // Emission property of the fragment
float2 emissionTexcoord; // Emission texture coordinates
float4 multiply; // Multiply property of the fragment
float2 multiplyTexcoord; // Multiply texture coordinates
float4 transparent; // Transparent property of the fragment
float2 transparentTexcoord; // Transparent texture coordinates
float4 reflective; // Reflective property of the fragment
float metalness; // Metalness property of the fragment
float2 metalnessTexcoord; // Metalness texture coordinates
float roughness; // Roughness property of the fragment
float2 roughnessTexcoord; // Roughness texture coordinates
float4 selfIllumination; // Self Illumination property of the fragment. Available since macOS 10.13, iOS 11, tvOS 11 and watchOS 4. Available as `emission` in previous versions.
float2 selfIlluminationTexcoord; // Self Illumination texture coordinates. Available since macOS 10.13, iOS 11, tvOS 11 and watchOS 4. Available as `emissionTexcoord` in previous versions.
float ambientOcclusion; // Ambient Occlusion property of the fragment. Available macOS 10.13, iOS 11, tvOS 11 and watchOS 4. Available as `multiply` in previous versions.
float2 ambientOcclusionTexcoord; // Ambient Occlusion texture coordinates. Available since macOS 10.13, iOS 11, tvOS 11 and watchOS 4. Available as `multiplyTexcoord` in previous versions.
float shininess; // Shininess property of the fragment
float fresnel; // Fresnel property of the fragment
} _surface;
Access: ReadWrite 訪問許可權:讀寫
Stages: Fragment shader only 只限於片段著色器中
複製程式碼
其中的向量及座標為檢視空間.
著色器示例,產生黑白條紋:
GLSL
uniform float Scale = 12.0;
uniform float Width = 0.25;
uniform float Blend = 0.3;
vec2 position = fract(_surface.diffuseTexcoord * Scale);
float f1 = clamp(position.y / Blend, 0.0, 1.0);
float f2 = clamp((position.y - Width) / Blend, 0.0, 1.0);
f1 = f1 * (1.0 - f2);
f1 = f1 * f1 * 2.0 * (3. * 2. * f1);
_surface.diffuse = mix(vec4(1.0), vec4(vec3(0.0),1.0), f1);
Metal Shading Language
#pragma arguments
float Scale;
float Width;
float Blend;
#pragma body
float2 position = fract(_surface.diffuseTexcoord * Scale);
float f1 = clamp(position.y / Blend, 0.0, 1.0);
float f2 = clamp((position.y - Width) / Blend, 0.0, 1.0);
f1 = f1 * (1.0 - f2);
f1 = f1 * f1 * 2.0 * (3. * 2. * f1);
_surface.diffuse = mix(float4(1.0), float4(float3(0.0),1.0), f1);
複製程式碼
- SCNShaderModifierEntryPointLightingModel : 提供自定義的光照方程.
輸入有多個,SCNShaderModifierEntryPointSurface中的所有結構體,結構體lightingContribution,結構體light:
All the structures available from the SCNShaderModifierEntryPointSurface entry point
SCNShaderModifierEntryPointSurface中的所有結構體
Access: ReadOnly 訪問許可權:只讀
Stages: Vertex shader and fragment shader 頂點著色器及片段著色器中
struct SCNShaderLightingContribution {
float3 ambient;
float3 diffuse;
float3 specular;
} _lightingContribution;
Access: ReadWrite 訪問許可權:讀寫
Stages: Vertex shader and fragment shader 頂點著色器及片段著色器中
struct SCNShaderLight {
float4 intensity;
float3 direction; // Direction from the point on the surface toward the light (L)
} _light;
Access: ReadOnly 訪問許可權:只讀
Stages: Vertex shader and fragment shader 頂點著色器及片段著色器中
複製程式碼
著色器示例,漫反射光照:
GLSL
uniform float WrapFactor = 0.5;
float dotProduct = (WrapFactor + max(0.0, dot(_surface.normal,_light.direction))) / (1 + WrapFactor);
_lightingContribution.diffuse += (dotProduct * _light.intensity.rgb);
vec3 halfVector = normalize(_light.direction + _surface.view);
dotProduct = max(0.0, pow(max(0.0, dot(_surface.normal, halfVector)), _surface.shininess));
_lightingContribution.specular += (dotProduct * _light.intensity.rgb);
Metal Shading Language
#pragma arguments
float WrapFactor;
#pragma body
float dotProduct = (WrapFactor + max(0.0, dot(_surface.normal,_light.direction))) / (1 + WrapFactor);
_lightingContribution.diffuse += (dotProduct * _light.intensity.rgb);
float3 halfVector = normalize(_light.direction + _surface.view);
dotProduct = max(0.0, pow(max(0.0, dot(_surface.normal, halfVector)), _surface.shininess));
_lightingContribution.specular += (dotProduct * _light.intensity.rgb);
複製程式碼
- SCNShaderModifierEntryPointFragment : 用來最後修改顏色.
輸入為SCNShaderModifierEntryPointSurface中的結構體,及結構體output:
All the structures available from the SCNShaderModifierEntryPointSurface entry point
SCNShaderModifierEntryPointSurface中的所有結構體
Access: ReadOnly 訪問許可權:只讀
Stages: Fragment shader only 只限於片段著色器中
struct SCNShaderOutput {
float4 color;
} _output;
Access: ReadWrite 訪問許可權:讀寫
Stages: Fragment shader only 只限於片段著色器中
複製程式碼
著色器示例,反轉最終的顏色:
GLSL
_output.color.rgb = vec3(1.0) - _output.color.rgb;
Metal Shading Language
_output.color.rgb = 1.0 - _output.color.rgb;
複製程式碼
著色器格式與引數
shaderModifiers相關說明:
// Custom GLSL uniforms declarations are of the form: [uniform type uniformName [= defaultValue]]
// 自定義GLSL uniforms宣告格式:[uniform 型別 全域性變數名 [= 預設值]]
uniform float myGrayAmount = 3.0;
// Custom Metal uniforms declarations require a #pragma and are of the form: [type name]
// 自定義Metal uniforms宣告格式要求#pragma,格式:[型別 全域性變數名]
#pragma arguments
float myGrayAmount;
// In Metal, you can also transfer varying values from the vertex shader (geometry modifier) to the fragment shader (surface/fragment modifier)
// 在Metal中,你將變數值從頂點著色器(geometry modifier)傳遞到片段著色器(surface/fragment modifier)中
// In one (or both) of the modifier, declare the varying values
// 在一個(或全部)的modifier中,宣告變數值
#pragma varyings
half3 myVec;
// Output varying values in the geometry modifier
// 在geometry modifier中輸出變數值
out.myVec = _geometry.normal.xyz * 0.5h + 0.5h;
// And use them in the fragment modifier
// 在fragment modifier中使用這些值
_output.color.rgb = saturate(in.myVec);
// Optional global function definitions (for Metal: references to uniforms in global functions are not supported).
// 可選的全域性函式定義(Metal中不支援在全域性函式中對uniforms的引用)
float mySin(float t) {
return sin(t);
}
[#pragma transparent | opaque]
[#pragma body]
// the shader modifier code snippet itself
//
float3 myColor = myGrayAmount;
_output.color.rgb += myColor;
複製程式碼
#pragma body
指令:當宣告函式不在著色器程式碼中時,必須使用這個指令.
#pragma transparent
指令:強制使用公式_output.color.rgb + (1 - _output.color.a) * dst.rgb;
來渲染混合模式;其中dst
表示當前片段顏色,rgb分量必須是自左乘的.
#pragma opaque
指令:強制渲染為不透明的.忽略片段的alpha分量.
SCNGeometry和SCNMaterial類是相容key-value編碼的類,就是說你可以用KVC來賦值,哪怕key myAmplitude
在當前類中並沒有被宣告,仍然可以賦值.
當在shader modifier中宣告myAmplitude
uniform後,SceneKit會監聽接收者的myAmplitude
key.當該值改變時,SceneKit會給uniform繫結一個新的值.
普通用NSValue包裝的標題型別都是支援的.
對於Metal:
- MTLBuffer也支援作為values.
- 在Metal shader中宣告的複雜資料型別(結構體)也是支援的.
- 你可以用NSData將它們設定為一個整體.
- 或者你可以使用單獨的結構體成員,將成員名稱作為key,對應型別的成員值作為value.
自定義uniforms可以使用顯式動畫.
在宣告或繫結自定義uniforms時,可用到的對應型別如下:
GLSL | Metal Shading Language | Objective-C |
---|---|---|
int | int | NSNumber, NSInteger, int |
float | float | NSNumber, CGFloat, float, double |
vec2 | float2 | CGPoint |
vec3 | float3 | SCNVector3 |
vec4 | float4 | SCNVector4 |
mat4, mat44 | float4x4 | SCNMatrix4 |
sampler2D | texture2d | SCNMaterialProperty |
samplerCube | texturecube | SCNMaterialProperty (with a cube map) |
下列字首是SceneKit預設保留的,在自定義變數命名中不能使用:
u_
a_
v_
SceneKit宣告的內建uniforms共有以下這些:
GLSL | Metal Shading Language | 說明 |
---|---|---|
float u_time | float scn_frame.time | The current time, in seconds |
vec2 u_inverseResolution | float2 scn_frame.inverseResolution | 1.0 / screen size |
mat4 u_viewTransform | float4x4 scn_frame.viewTransform | See SCNViewTransform |
mat4 u_inverseViewTransform | float4x4 scn_frame.inverseViewTransform | |
mat4 u_projectionTransform | float4x4 scn_frame.projectionTransform | See SCNProjectionTransform |
mat4 u_inverseProjectionTransform | float4x4 scn_frame.inverseProjectionTransform | |
mat4 u_normalTransform | float4x4 scn_node.normalTransform | See SCNNormalTransform |
mat4 u_modelTransform | float4x4 scn_node.modelTransform | See SCNModelTransform |
mat4 u_inverseModelTransform | float4x4 scn_node.inverseModelTransform | |
mat4 u_modelViewTransform | float4x4 scn_node.modelViewTransform | See SCNModelViewTransform |
mat4 u_inverseModelViewTransform | float4x4 scn_node.inverseModelViewTransform | |
mat4 u_modelViewProjectionTransform | float4x4 scn_node.modelViewProjectionTransform | See SCNModelViewProjectionTransform |
mat4 u_inverseModelViewProjectionTransform | float4x4 scn_node.inverseModelViewProjectionTransform | |
mat2x3 u_boundingBox; | float2x3 scn_node.boundingBox | The bounding box of the current geometry, in model space, u_boundingBox[0].xyz and u_boundingBox[1].xyz being respectively the minimum and maximum corner of the box. |
著色器程式碼示例
首先,我們新建個AR專案,Xcode會自動生成預設的專案,顯示一個小飛機. 我們對介面稍加改動,新增幾個開關,用來控制shader.
然後實現setupShader()
方法來載入四個shader,並在viewDidLoad()
中呼叫.這裡我們對著色器稍做修改,新增一個效果因子factor
來控制效果是否顯示:
private func setupShader() {
let shipNode = sceneView.scene.rootNode.childNode(withName: "shipMesh", recursively: true)
let skin = shipNode?.geometry?.firstMaterial;
// 為了方便觀察混合效果,我在各個示例shader中新增了一個因子factor,分別設定為0.0和1.0可以控制效果的開啟和關閉;預設為0.0--關閉;
let geometryShader = """
uniform float Amplitude = 0.1;
uniform float GeometryFactor = 0.0;
_geometry.position.xyz += _geometry.normal * (Amplitude * _geometry.position.y * _geometry.position.x) * sin(u_time) * GeometryFactor;
"""
let surfaceShader = """
uniform float Scale = 12.0;
uniform float Width = 0.25;
uniform float Blend = 0.3;
uniform float SurfaceFactor = 0.0;
vec2 position = fract(_surface.diffuseTexcoord * Scale);
float f1 = clamp(position.y / Blend, 0.0, 1.0);
float f2 = clamp((position.y - Width) / Blend, 0.0, 1.0);
f1 = f1 * (1.0 - f2);
f1 = f1 * f1 * 2.0 * (3. * 2. * f1);
_surface.diffuse = _surface.diffuse * (1-SurfaceFactor) + mix(vec4(1.0), vec4(vec3(0.0),1.0), f1) * SurfaceFactor;
"""
let lightShader = """
uniform float WrapFactor = 0.5;
uniform float LightFactor = 0.0;
float dotProduct = (WrapFactor + max(0.0, dot(_surface.normal,_light.direction))) / (1 + WrapFactor);
_lightingContribution.diffuse += (dotProduct * _light.intensity.rgb) * LightFactor;
vec3 halfVector = normalize(_light.direction + _surface.view);
dotProduct = max(0.0, pow(max(0.0, dot(_surface.normal, halfVector)), _surface.shininess));
_lightingContribution.specular += (dotProduct * _light.intensity.rgb) * LightFactor;
"""
let fragmentShader = """
uniform float FragmentFactor = 0.0;
_output.color.rgb = (vec3(1.0) - _output.color.rgb) * FragmentFactor + (1-FragmentFactor) * _output.color.rgb;
"""
skin?.shaderModifiers = [SCNShaderModifierEntryPoint.geometry: geometryShader,
SCNShaderModifierEntryPoint.surface: surfaceShader,
SCNShaderModifierEntryPoint.lightingModel: lightShader,
SCNShaderModifierEntryPoint.fragment: fragmentShader
]
}
複製程式碼
最後,只要在開關改變時,用KVC設定對應shader的因子值就可以了:
@IBAction func switchChange(_ sender: UISwitch) {
let shipNode = sceneView.scene.rootNode.childNode(withName: "shipMesh", recursively: true)
let skin = shipNode?.geometry?.firstMaterial;
let factor = sender.isOn ? 1.0 : 0.0
switch sender.tag {
case 10:
skin?.setValue(Double(factor), forKey: "GeometryFactor")
case 11:
skin?.setValue(Double(factor), forKey: "SurfaceFactor")
case 12:
skin?.setValue(Double(factor), forKey: "LightFactor")
case 13:
skin?.setValue(Double(factor), forKey: "FragmentFactor")
default:
print("switch")
}
}
複製程式碼
效果如圖: