“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第3篇文章,点击查看活动详情”
正文
下面通过一个简单的颜色混合着色器示例介绍基本的 OSL 着色器语法。关于这个主题的更综合的解释可以在这里阅读。
注意
-
强烈建议熟悉基本的 C 语言语法,因为它是常用的着色语言的基础,如 OSL、HLSL 和 GLSL。
-
关于编写 OSL 着色器的更多详细信息可以在 ImageWorks 的 OSL GitHub 的 OSL-languagespec PDF 文档中找到。
示例代码
这个示例着色器根据 表面观察角度 (surface viewing angle)【又名 “面比”(facing ratio) 或 “入射角”(incident angle) 或 “垂直-平行”(Perpendicular-Parallel) 】混合了 2 个颜色源。用户可以选择正面(front)颜色或纹理,侧面颜色或纹理,着色器的输出将是这两种输入的混合【取决于观察表面的角度】。
这是着色器的完整代码,你也可以在这里下载。
#include "stdosl.h"
shader cglColorAngleBlend
[[ string help = "Blend colors by view angle" ]]
(
color facing_color = color(0, 0, 0)
[[ string help = "The color (or texture) that will appear at perpendicular view angle" ]],
color side_color = color(1, 1, 1)
[[ string help = "The color (or texture) that will appear at grazing view angle" ]],
float base_blend = 0.0
[[ string help = "The percent of side_color that is mixed with facing_color at perpendicular view angle",
float min = 0.0, float max = 1.0]],
float curve_exponent = 1.0
[[ string help = "A power exponent value by which the blend value is raised to control the blend curve",
float min = 0.001, float max = 10.0]],
output color color_out = color(1, 1, 1))
{
// calculate the linear facing ratio:
float facing_ratio = acos(abs(dot(-I,N))) / M_PI_2;
// calculate the curve facing ratio:
float final_blend_ratio = pow(facing_ratio , curve_exponent);
// blend the facing color:
color final_facing_color = (facing_color * (1 - base_blend)) + (side_color * base_blend);
// blend and output the final color:
color_out = ((final_facing_color * (1 - final_blend_ratio)) + (side_color * final_blend_ratio));
}
#include 语句
#include "stdosl.h"
#include 语句是一个标准的 C 编译器指令,用于将 OSL 源代码库代码文件 stdosl.h 链接到着色器的代码,这样代码中的 OSL 数据类型和函数就会被识别出来。
有些系统没有这个语句也能成功编译代码。我不确定它们的编译器是否自动链接 stdosl.h。
[[...]] 语句
[[ string help = "The percent of side_color that is mixed with facing_color at perpendicular view angle", float min = 0.0, float max = 1.0]]
双方括号语句为 着色器参数 提供了 帮助注解 和 限制值范围 的作用:
不是所有支持 OSL 的材质系统都实现了 通过主机软件生成的 着色器接口中的注解(着色器仍可工作,但它的参数不会被描述并被限制值在定义的范围内)。
输入、输出参数和默认值
除了 #include 语句、注解和注释,我们可以看到,OSL 着色器的结构非常类似于 C 函数:
shader <identifier> (input/output parameters) {code}
shader cglColorAngleBlend(
color facing_color = color(0, 0, 0),
color side_color = color(1, 1, 1),
float base_blend = 0.0,
float curve_exponent = 1.0,
output color color_out = color(1, 1, 1))
{
float facing_ratio = acos(abs(dot(-I,N))) / M_PI_2;
float final_blend_ratio = pow(facing_ratio , curve_exponent);
color final_facing_color = (facing_color * (1 - base_blend)) + (side_color * base_blend);
color_out = ((final_facing_color * (1 - final_blend_ratio)) + (side_color * final_blend_ratio));
}
首先是 数据类型,在本例中是 shader,然后是 shader 标识符,在本例中是 cglColorAngleBlend:
shader cglColorAngleBlend
在着色器的 类型 和 标识符 之后,一个参数列表被定义在圆括号内,用逗号分隔。这些参数定义了着色器的 输入、输出 和 默认值。输出 参数的前面有 output 修饰符。
(
<parameter type> <parameter identifier> = <parameter default value>,
<parameter type> <parameter identifier> = <parameter default value>,
output <parameter type> <parameter identifier> = <parameter default value>
)
(
color facing_color = color(0, 0, 0),
color side_color = color(1, 1, 1),
float base_blend = 0.0,
float curve_exponent = 1.0,
output color color_out = color(1, 1, 1)
)
在这种情况下,着色器有 4 个用户输入参数,和 1 个输出参数。
2 个 color 类型参数,facing_color 和 side_color 用于将正面色和侧面色混合在一起,一个 float 参数 base_blend 指定将与正面色混合的侧面色的数量,而不管视角角度如何,第二个 float 参数 curve_exponent 指定将提高混合值以创建非线性混合曲线的幂指数。
output 参数 color_out 是一个将由着色器计算的颜色。
注意,即使
output参数将由着色器计算,它还是需要为着色器编译定义一个默认值。
代码指令剖析
在着色器参数之后,花括号括起来的是着色器代码的实际主体,包含指令,每个指令以分号结尾
{
<instruction>;
<instruction>;
……
}
{
float facing_ratio = acos(abs(dot(-I,N))) / M_PI_2;
float final_blend_ratio = pow(facing_ratio , curve_exponent);
color final_facing_color = (facing_color * (1 - base_blend)) + (side_color * base_blend);
color_out = ((final_facing_color * (1 - final_blend_ratio)) + (side_color * final_blend_ratio));
}
在我们的着色器的情况下,第一个代码指令是定义一个新的 float 内部变量 facing_ratio,计算表面/视角,并将结果值赋给它:
float facing_ratio = acos(abs(dot(-I,N))) / M_PI_2;
表达式:
acos( abs( dot( -I, N ) ) ) / M_PI_2
计算入射角,即两个矢量之间的夹角,从表面阴影点出发,一个指向入射光线的原点,另一个指向表面法线,作为 O 到 1 的因子,代表 0 - 90 度。这两个向量很容易通过 OSL 内置的全局变量 N 和 I 获得,N 是阴影点的表面法线,I 是指向阴影点的入射射线向量,在本例中,通过在阴影点前面键入减号:-I,可以反向指向阴影点。
入射角以弧度计算,正如 N 和 -I 的点积的反余弦,然后除以 1/2*π ,将其转换为 0 到 1 的线性因子,以弧度表示 0 到 90 度,M_PI_2 是一个方便的半 π 常数。
M_PI是一个完整的 π ,M_2PI是 2*π 分别代表 180 度弧度和 360 度(OSL 提供在这个系列中有更多的常数)。
第二条指令通过 curve_exponent 输入参数提供的幂值提高上一条指令中计算的面比 facing_ratio,以创建一个非线性角度/颜色混合值(不等于 1)。
修改后的混合值存储在一个新的内部变量 final_blend_ratio 中:
float final_blend_ratio = pow(facing_ratio , curve_exponent);
注意: 我们可以通过修改
facing_ratio变量的值来避免设置新变量,我们也可以将这两个指令合并成一个更大的表达式,像这样:
pow( acos( abs( dot( -I, N ) ) ) / M_PI_2 , curve_exponent )
但为了清晰起见,我决定把它分成两个变量和两个指令。
第三条指令 修改输入颜色 facing_color,根据输入参数 base_blend 给出的百分比将其与输入颜色 side_color 预混合,并将结果颜色赋给名为 final_facing_color 的新内部变量:
color final_facing_color = (facing_color * (1 - base_blend)) + (side_color * base_blend);
表达式为:
( facing_color * ( 1 – base_blend ) ) + ( side_color * base_blend )
计算两个输入颜色之间的线性组合(线性插值),使用 base_blend 作为它们之间的 0 - 1 混合因子。
注意:OSL 允许在颜色和浮点数之间自由定义算术操作。
第四个也是最后一个指令 在 final_facing_color 变量中存储了由修改过的面色 (facing color) 和输入颜色参数 (side_color) 给出的边色 (side color) 之间创建的最终混合,同样,通过计算两个颜色之间的 线性组合,这次使用我们之前计算过的 final_blend_ratio 变量值作为 组合因子,非常重要的是,最后,将混合结果赋给着色器 output 参数 color_out,这样它将成为着色器的最终输出:
color_out = ((final_facing_color * (1 - final_blend_ratio)) + (side_color * final_blend_ratio));
这个屏幕截图显示了这个着色器在 Blender 和 Cycles 中工作,连接到一个 Pricipled BSDF 着色器作为它的基础颜色源: