这是我参与8月更文挑战的第31天,活动详情查看: 8月更文挑战
Spark AR 是 Facebook 免费创作 AR 作品的平台,使用户能够为 Facebook 和 Instagram 创建交互式增强现实体验,超过 40 万名创作者,190个国家/地区,使用 Spark AR 来创作自己的AR作品
由于该软件无需任何编码知识即可使用,因此任何人现在都可以在 AR 世界中几乎没有经验地制作下一个疯狂式传播的 Instagram AR 特效,引领世界潮流。
专门的 AR 滤镜设计师单价甚至可达到 1000 美元到 3 万美元不等。
着色器代码资源可以让你在 Spark AR Studio 中编写自定义着色器。
如果你已经创建了一个完整的着色器(Shader)其返回 vec4 颜色或有一个 out vec4 颜色参数,它就可以作为一个材质,称为着色器代码资产材质。
对于一个更模块化的方法,你可以在贴片(Patch)编辑器中实例化着色器代码资产,并将其作为贴片。我们将在本文中介绍这两种方法。
创建着色器代码资产
创建一个着色器代码资产:
- 在 Spark AR Studio 中,进入资产面板。
- 单击 Add Asset,并从菜单中选择 Shader Code Asset。
着色器代码资源将在资产面板中列出。一个 .sca 文件将被添加到项目中。编辑着色器代码资产:
- 在资源面板中选择着色器代码资源。
- 在检查器中,单击Edit。
您对文件所做的更改将在保存后反映在 Spark AR Studio 中。保存时,任何编译错误或警告都会出现在控制台中。
定义函数
着色器必须定义一个主函数。这个函数的输入和输出将定义着色器代码资产的接口,其在 Spark AR Studio 中作为材质或贴片使用。例如,当带有下面签名的着色器代码资产实例化为一个贴片时,将有一个单一的输入 Alpha 和一个单一的输出 Color:
void main(float Alpha, out vec4 Color);
当作为材质使用时,材质只有一个输入值,Alpha。颜色参数将是材质的颜色。
如果没有找到名为'main'的函数,则文件中的最后一个函数将被视为main 函数。
学习更多关于着色器语言的知识。
着色器代码资产贴片
着色器代码资产贴片 与 现有的可视化着色器贴片 和 其他着色器代码贴片 可互操作。
在贴片编辑器中使用着色器代码资源:
- 创建一个着色器代码资产;
- 打开贴片编辑器;
- 从资产面板中将着色器代码资源拖到贴片编辑器中。
生成的贴片代表了着色器的 main 函数。
然而,如果前缀有一个 export 限定符,着色器中的任何函数都可以作为着色器代码资产。
在下面的例子中,star() 和 circle() 函数都被导出,并生成可用的贴片资源,这些资源可以从资产面板中拖到贴片编辑器中。
#import <gradients>
#import <sdf>
vec4 drawSdf(float dist, vec2 uv) {
float edge = fwidth(dist);
float alpha = smoothstep(-edge, +edge, dist);
vec4 color = mix(0x00FFFFFF, 0x0000C0FF, std::gradientHorizontal(uv));
return mix(color, color.rgb0, alpha);
}
export vec4 star() {
auto sdf = std::sdfStarSharp(vec2(0.5, 0.5), 0.25, 0.50, 5.0);
vec2 uv = fragment(std::getVertexTexCoord());
float dist = sdf(uv);
return drawSdf(dist, uv);
}
export vec4 circle() {
auto sdf = std::sdfCircle(vec2(0.5, 0.5), 0.25);
vec2 uv = fragment(std::getVertexTexCoord());
float dist = sdf(uv);
return drawSdf(dist, uv);
}
输入与输出
贴片的输入和输出是由着色器的 main 函数决定的。输入参数将显示为输入端口,输出参数将显示为输出端口。若要将参数标记为输出,请在其前面加上 out 限定符。如果着色器有一个返回类型而不是void,这也将作为输出端口出现。
与着色器代码资产材质一样,输入端口的默认值可以使用注解(annotation)指定。
着色器代码资产材质
任何输出 vec4 颜色值的着色器代码资产,无论是通过输出参数还是作为返回值,都可以直接作为材质使用。
使用着色器代码资产直接作为材质:
- 创建一个新材质。
- 在检查器中,在Shader Type下拉菜单中选择 shader 资源。
要对材质执行顶点位移,你可以通过创建一个额外的 vec4 out 参数并命名为Position来直接写入顶点位置。
基础例子
在这个例子中,我们使用了着色器代码资源来创建一个具有尖边的彩色心形:
这个例子是用下面的着色器创建的:
#import <gradients>
#import <sdf>
vec2 heartify(vec2 uv, vec2 pivot, float w, float scale, float offset) {
float dx = abs(uv.x - pivot.x);
return vec2(uv.x, dx * (w - dx) + (uv.y - pivot.y) * scale - offset + pivot.y);
}
// @param[default=#FF0000FF] color1
// @param[default=#0000C0FF] color2
// @param[default=0.5,min=0.0,max=1.0] spikiness
// @return color
vec4 main(vec4 color1, vec4 color2, float spikiness) {
vec2 uv = fragment(std::getVertexTexCoord());
uv = heartify(uv, vec2(0.5, 0.5), 1.15, 1.3, 0.1);
vec4 color = mix(color1, color2, std::gradientHorizontal(uv));
float innerRadius = mix(0.50, 0.25, spikiness);
auto sdf = std::sdfStarSharp(vec2(0.5, 0.5), innerRadius, 0.50, 25.0);
float dist = sdf(uv);
float edge = fwidth(dist);
float alpha = smoothstep(-edge, +edge, dist);
return mix(color, color.rgb0, alpha);
}
本例中的 main 函数返回一个 vec4 并接受三个参数,两个 vec4 指定形状的两种颜色,以及一个 float spikiness,它指定 spikes 的大小。
因此,当着色器直接作为材质应用时,你会在检查器中看到这些值:
在 Parameters 下,三个输入变量的默认值对应于main 函数注解中指定的值。你可以在检查器中修改这些属性,或者在脚本中对材质调用 setParameter 方法。
SDF 模块提供的 std::sdfStarSharp 函数创建一个星形的 SDF。
要使用这个函数,这个模块必须使用在着色器顶部的 import 语句来导入。
高级例子
在这个例子中,我们将在 Spark AR Studio 中实现一个响应光的phong 材质,同时覆盖 Spark 特有的着色语言特性。生成的材质支持可选的漫反射、镜面和发光纹理,并可以响应定向光、聚光、点光和环境光。
#import <lights>
struct PhongMaterialParameters {
vec3 emission;
vec3 ambientFactor;
vec3 diffuseFactor;
vec3 specularFactor;
float shininess;
float occlusion;
};
vec3 applyPhong(
std::LightData light,
vec3 normal,
vec3 view,
PhongMaterialParameters material) {
vec3 reflected = -reflect(light.toLightDirection, normal);
float LdotN = dot(light.toLightDirection, normal);
float RdotV = max(dot(reflected, view), 0.0);
float diffuseFactor = max(LdotN, 0.0);
vec3 diffuse = material.diffuseFactor * (light.intensity * diffuseFactor);
float specularFactor = pow(RdotV, material.shininess) * step(0.0, LdotN); // do not light backface
vec3 specular = material.specularFactor * (light.intensity * specularFactor);
return material.occlusion * diffuse + specular;
}
// A material that uses the Phong shading model.
//
// @param [default=0.0, min=0.0, max=100.0] smoothness
void main(optional<std::Texture2d> diffuseTexture,
optional<std::Texture2d> normalTexture,
optional<std::Texture2d> specularTexture,
optional<std::Texture2d> emissiveTexture,
float smoothness,
out vec4 Position,
out vec4 Color) {
// non-linear mapping from [0,100] to [1,100]
float shininess = mix(1.0, 100.0, pow(smoothness * 0.01, 2.0));
// Attributes
vec2 uv = std::getVertexTexCoord();
optional<vec3> sampledNormal = normalize(std::getTangentFrame() * (normalTexture.sample(uv).xyz * 2.0 - 1.0));
vec3 localNormal = sampledNormal.valueOr(std::getVertexNormal());
vec4 localPosition = std::getVertexPosition();
// Material parameters
vec4 diffuseAndOpacity = diffuseTexture.sample(uv).valueOr(vec4(1.0));
vec4 specularAndShininess = specularTexture.sample(uv).valueOr(vec4(1.0));
PhongMaterialParameters material;
material.emission = emissiveTexture.sample(uv).rgb.valueOr(vec3(0.0));
material.ambientFactor = diffuseAndOpacity.rgb;
material.diffuseFactor = diffuseAndOpacity.rgb;
material.specularFactor = specularAndShininess.rgb;
material.shininess = clamp(specularAndShininess.a * shininess, 1.0, 100.0);
material.occlusion = 1.0;
// Screen-space position
Position = std::getModelViewProjectionMatrix() * localPosition;
// Camera-space normal, position, and view
vec3 csNormal = normalize(fragment(std::getNormalMatrix() * localNormal));
vec4 csPosition = fragment(std::getModelViewMatrix() * localPosition);
vec3 csView = normalize(-csPosition.xyz); // csCamera is at vec3(0,0,0)
// color
vec3 color = material.emission + material.ambientFactor * std::getAmbientLight().rgb;
if (std::getActiveLightCount() > 0) color += applyPhong(std::getLightData0(csPosition.xyz), csNormal, csView, material);
if (std::getActiveLightCount() > 1) color += applyPhong(std::getLightData1(csPosition.xyz), csNormal, csView, material);
if (std::getActiveLightCount() > 2) color += applyPhong(std::getLightData2(csPosition.xyz), csNormal, csView, material);
if (std::getActiveLightCount() > 3) color += applyPhong(std::getLightData3(csPosition.xyz), csNormal, csView, material);
Color = vec4(color, diffuseAndOpacity.a);
}
main 函数接受一系列可选的 std::Texture2d 类型参数。Texture2d结构体提供了一种将纹理传递给 SparkSL 中的着色器的方法,并包含纹理采样函数。
在数据类型中使用 optional 关键字是 SparkSL 的一种语言特性。可选类型的变量不要求它的值出现在着色器中。相反,可选变量的值必须通过 valueOr 函数访问,这需要指定一个回退值。如果在着色器中找不到变量,将使用回退值。
例如,当采样 normalTexture 时,后续计算的结果被标记为可选。当这个可选值在下一行被访问时,valueOr 函数被使用,如果采样的法线值不在着色器中的话,顶点法线被用作一个回退值。
当作为一种材质使用时,检查面板将会是这样的:
平滑参数是一个滑块,因为最小/最大值是作为注解提供的。完整的材质看起来是这样的:
在这个例子中,我们使用了定向光和聚光灯:
light api 函数的索引是由场景中灯光的顺序所决定的。所以在我们的例子中,行 std::getLightData0(cposition .xyz) 返回方向光,而 std::getLightData1(cposition .xyz) 返回聚光灯,激活的光计数为 2。