// Micro Scratched Texture
//
// Author: John Su <cuckons@gmail.com>
// Changes by Michael Abrahams <miabraha@gmail.com>
// Last Modified 1/11/2021 for Redshift 3D + Metadata & Triplaner
// An OSL shader that generates texture to make swirly micro scratch look by
// controlling the anistropy and roughness.
// The original idea is from Hang Li(悬挂鲤) and Ben Paschke. Here I implemented
// it using OSL and exposured some arts friendly parameters.
float AxisMask(point axis, float power)
{
return pow(abs(dot(N, axis)), power);
}
float lineDistance(point p, float direction){
float theta = direction * M_2PI;
float s = 0, c = 1;
sincos(theta, s, c);
vector v = vector(c,s,0);
float distance = length(p - (v * dot(v, p)));
return distance;
}
void generateScratchPlane(point shading_p,
int max_search_cell,
point index,
int seed,
float delta,
float hardScratches,
float roughness_default,
float roughness_min,
float roughness_max,
float anisotropic_min,
float anisotropic_max,
float scratchWidth,
output float roughness, output float anisotropy, output float rotation, output float scratch)
{
for (int x = -max_search_cell; x <= max_search_cell; ++x){
for (int y = -max_search_cell; y <= max_search_cell; ++y){
// Distance from line in neighbor cell
point line_cell_index = index + point(x, y, 0);
point scratch_origin_p = (line_cell_index + (hashnoise(line_cell_index + seed + .8) - 0.5)) * delta;
rotation = hashnoise(line_cell_index + seed + .88);
float dist = lineDistance(scratch_origin_p - shading_p, rotation);
// Randomize width
float width = scratchWidth * hashnoise(line_cell_index + seed + .888) / 2048;
// hard = 0-1 step function; soft = linear gradient
if (hardScratches) {
scratch = 1 - step(width, dist);
}
else {
scratch = 1 - min(dist / width, 1);
}
if (scratch)
{
roughness = select(
roughness_default,
mix(roughness_min, roughness_max, hashnoise(line_cell_index + seed + .8888)),
scratch > 0
);
anisotropy = select(
0,
mix(anisotropic_min, anisotropic_max, hashnoise(line_cell_index + seed + .88888)),
scratch > 0
);
rotation *= scratch;
return;
}
}
}
}
shader Scratches
[[ string help = "Procedural Scratches",
string label = "Scratches" ]]
(
float density=10
[[string label="Density",
string widget="number",
float min=1,
float max=50]],
float scale=5
[[string label="Scale",
string widget="number",
float min=.01,
float max=100]],
float scratchLength=10
[[string label="Scratch Length",
string widget="number",
float min=0,
float max=10]],
float scratchWidth=1
[[string label="Scratch Width",
string widget="number",
float min=0,
float max=10]],
int hardScratches=1
[[string label="Hard scratches",
string widget="checkBox"]],
float roughness_default = 0.0
[[string label="Default Roughness",
string widget="number",
float min=0,
float max=1]],
float roughness_min=0.1
[[string label="Minimum Roughness",
string widget="number",
float min=0,
float max=1]],
float roughness_max=0.2
[[string label="Maximum Roughness",
string widget="number",
float min=0,
float max=1]],
float anisotropic_min = 0.0
[[string label="Minimum Anisotropy",
string widget="number",
float min=0,
float max=1]],
float anisotropic_max = 0.2
[[string label="Maximum Anisotropy",
string widget="number",
float min=0,
float max=1]],
float blendPower = 1,
int seed=12345
[[string label="Random Seed",
string widget="number",
int min=0,
int max=99999]],
output float roughness = 0 + roughness_default,
output float anisotropy = 0,
output float rotation = 0,
output float scratch = 0
){
// Divides texture space into a grid
// Each cell contains a scratch that will be offset by a hash noise.
// "density" and "scale" both effectively scale the shader,
// but density preserves scratch length.
float delta = 1/max(density * density * scale, .1);
int max_search_cell = (int)ceil(scratchLength);
//XY
point xy = point(P[0], P[1], 0)*scale;
point xyIndex = point(round(scale * P[0]/delta), round(scale * P[1]/delta), 0.0);
float xyPlaneRoughness = 0;
float xyPlaneAnisotropy = 0;
float xyPlaneRotation = 0;
float xyPlaneScratch = 0;
generateScratchPlane(xy, max_search_cell, xyIndex, seed, delta, hardScratches, roughness_default, roughness_min, roughness_max, anisotropic_min, anisotropic_max, scratchWidth, xyPlaneRoughness, xyPlaneAnisotropy, xyPlaneRotation, xyPlaneScratch);
//XZ
point xz = point(P[0], P[2], 0)*scale;
point xzIndex = point(round(scale * P[0]/delta), round(scale * P[2]/delta), 0.0);
float xzPlaneRoughness = 0;
float xzPlaneAnisotropy = 0;
float xzPlaneRotation = 0;
float xzPlaneScratch = 0;
generateScratchPlane(xz, max_search_cell, xzIndex, seed + 1, delta, hardScratches, roughness_default, roughness_min, roughness_max, anisotropic_min, anisotropic_max, scratchWidth, xzPlaneRoughness, xzPlaneAnisotropy, xzPlaneRotation, xzPlaneScratch);
//YZ
point yz = point(P[1], P[2], 0)*scale;
point yzIndex = point(round(scale * P[1]/delta), round(scale * P[2]/delta), 0.0);
float yzPlaneRoughness = 0;
float yzPlaneAnisotropy = 0;
float yzPlaneRotation = 0;
float yzPlaneScratch = 0;
generateScratchPlane(yz, max_search_cell, yzIndex, seed + 2, delta, hardScratches, roughness_default, roughness_min, roughness_max, anisotropic_min, anisotropic_max, scratchWidth, yzPlaneRoughness, yzPlaneAnisotropy, yzPlaneRotation, yzPlaneScratch);
roughness = mix(xyPlaneRoughness, xzPlaneRoughness, AxisMask(point(0,1,0), blendPower));
anisotropy = mix(xyPlaneAnisotropy, xzPlaneAnisotropy, AxisMask(point(0,1,0), blendPower));
rotation = mix(xyPlaneRotation, xzPlaneRotation, AxisMask(point(0,1,0), blendPower));
scratch = mix(xyPlaneScratch, xzPlaneScratch, AxisMask(point(0,1,0), blendPower));
roughness = mix(roughness, yzPlaneRoughness, AxisMask(point(1,0,0), blendPower));
anisotropy = mix(anisotropy, yzPlaneAnisotropy, AxisMask(point(1,0,0), blendPower));
rotation = mix(rotation, yzPlaneRotation, AxisMask(point(1,0,0), blendPower));
scratch = mix(scratch, yzPlaneScratch, AxisMask(point(1,0,0), blendPower));
}