Fix tiled perlin noise.
It turns out that the perlin implementation we inherited from WebKit does not actually generate tileable noise (see Chromium bug http://crbug.com/383495). The main problem is that when generating coordinates for gradient interpolation, it was attempting to wrap both x and (x + 1) simultaneously at the tile boundary (that is, either both or neither are wrapped). This obviously won't work, since along the tile seams, (x + 1) should be wrapped, but x should not. The same is true in y. This patch fixes both the CPU and GPU paths, renames some variables to more closely match the spec, and modifies the perlin noise GM to actually test tiling. (Note that the clipping the GM was doing was removed, since it's superfluous: it used to be necessary for image filters, but isn't anymore, and this isn't an image filter GM anyway.) R=sugoi@google.com, sugoi TBR=senorblanco Author: senorblanco@chromium.org Review URL: https://codereview.chromium.org/332523006
This commit is contained in:
parent
e61c411c12
commit
ce6a354e12
@ -78,3 +78,7 @@ dashcubics
|
||||
# dandov: Fix for bitmap shader by taking into account if the bitmap is alpha only
|
||||
# https://codereview.chromium.org/318923005/
|
||||
bitmapshaders
|
||||
|
||||
# Added by senorblanco for https://codereview.chromium.org/332523006/
|
||||
# Needs rebaseline for modified test cases
|
||||
perlinnoise
|
||||
|
@ -24,13 +24,11 @@ protected:
|
||||
return SkISize::Make(200, 500);
|
||||
}
|
||||
|
||||
void drawClippedRect(SkCanvas* canvas, int x, int y, const SkPaint& paint) {
|
||||
void drawRect(SkCanvas* canvas, int x, int y, const SkPaint& paint, const SkISize& size) {
|
||||
canvas->save();
|
||||
canvas->clipRect(SkRect::MakeXYWH(SkIntToScalar(x), SkIntToScalar(y),
|
||||
SkIntToScalar(fSize.width()), SkIntToScalar(fSize.height())));
|
||||
SkRect r = SkRect::MakeXYWH(SkIntToScalar(x), SkIntToScalar(y),
|
||||
SkIntToScalar(fSize.width()),
|
||||
SkIntToScalar(fSize.height()));
|
||||
canvas->translate(SkIntToScalar(x), SkIntToScalar(y));
|
||||
SkRect r = SkRect::MakeWH(SkIntToScalar(size.width()),
|
||||
SkIntToScalar(size.height()));
|
||||
canvas->drawRect(r, paint);
|
||||
canvas->restore();
|
||||
}
|
||||
@ -38,14 +36,25 @@ protected:
|
||||
void test(SkCanvas* canvas, int x, int y, SkPerlinNoiseShader::Type type,
|
||||
float baseFrequencyX, float baseFrequencyY, int numOctaves, float seed,
|
||||
bool stitchTiles) {
|
||||
SkISize tileSize = SkISize::Make(fSize.width() / 2, fSize.height() / 2);
|
||||
SkShader* shader = (type == SkPerlinNoiseShader::kFractalNoise_Type) ?
|
||||
SkPerlinNoiseShader::CreateFractalNoise(baseFrequencyX, baseFrequencyY, numOctaves,
|
||||
seed, stitchTiles ? &fSize : NULL) :
|
||||
seed, stitchTiles ? &tileSize : NULL) :
|
||||
SkPerlinNoiseShader::CreateTurbulence(baseFrequencyX, baseFrequencyY, numOctaves,
|
||||
seed, stitchTiles ? &fSize : NULL);
|
||||
seed, stitchTiles ? &tileSize : NULL);
|
||||
SkPaint paint;
|
||||
paint.setShader(shader)->unref();
|
||||
drawClippedRect(canvas, x, y, paint);
|
||||
if (stitchTiles) {
|
||||
drawRect(canvas, x, y, paint, tileSize);
|
||||
x += tileSize.width();
|
||||
drawRect(canvas, x, y, paint, tileSize);
|
||||
y += tileSize.width();
|
||||
drawRect(canvas, x, y, paint, tileSize);
|
||||
x -= tileSize.width();
|
||||
drawRect(canvas, x, y, paint, tileSize);
|
||||
} else {
|
||||
drawRect(canvas, x, y, paint, fSize);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void onDraw(SkCanvas* canvas) {
|
||||
@ -58,10 +67,10 @@ protected:
|
||||
test(canvas, 0, 100, SkPerlinNoiseShader::kFractalNoise_Type,
|
||||
0.1f, 0.1f, 2, 0, false);
|
||||
test(canvas, 100, 100, SkPerlinNoiseShader::kFractalNoise_Type,
|
||||
0.2f, 0.4f, 5, 0, true);
|
||||
0.05f, 0.1f, 1, 0, true);
|
||||
|
||||
test(canvas, 0, 200, SkPerlinNoiseShader::kTurbulence_Type,
|
||||
0.1f, 0.1f, 2, 0, true);
|
||||
0.1f, 0.1f, 1, 0, true);
|
||||
test(canvas, 100, 200, SkPerlinNoiseShader::kTurbulence_Type,
|
||||
0.2f, 0.4f, 5, 0, false);
|
||||
|
||||
@ -75,7 +84,7 @@ protected:
|
||||
test(canvas, 0, 400, SkPerlinNoiseShader::kFractalNoise_Type,
|
||||
0.1f, 0.1f, 2, 0, false);
|
||||
test(canvas, 100, 400, SkPerlinNoiseShader::kFractalNoise_Type,
|
||||
0.2f, 0.4f, 5, 0, true);
|
||||
0.1f, 0.05f, 1, 0, true);
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -39,9 +39,6 @@ inline int checkNoise(int noiseValue, int limitValue, int newValue) {
|
||||
if (noiseValue >= limitValue) {
|
||||
noiseValue -= newValue;
|
||||
}
|
||||
if (noiseValue >= limitValue - 1) {
|
||||
noiseValue -= newValue - 1;
|
||||
}
|
||||
return noiseValue;
|
||||
}
|
||||
|
||||
@ -320,12 +317,14 @@ SkScalar SkPerlinNoiseShader::PerlinNoiseShaderContext::noise2D(
|
||||
const StitchData& stitchData, const SkPoint& noiseVector) const {
|
||||
struct Noise {
|
||||
int noisePositionIntegerValue;
|
||||
int nextNoisePositionIntegerValue;
|
||||
SkScalar noisePositionFractionValue;
|
||||
Noise(SkScalar component)
|
||||
{
|
||||
SkScalar position = component + kPerlinNoise;
|
||||
noisePositionIntegerValue = SkScalarFloorToInt(position);
|
||||
noisePositionFractionValue = position - SkIntToScalar(noisePositionIntegerValue);
|
||||
nextNoisePositionIntegerValue = noisePositionIntegerValue + 1;
|
||||
}
|
||||
};
|
||||
Noise noiseX(noiseVector.x());
|
||||
@ -338,28 +337,36 @@ SkScalar SkPerlinNoiseShader::PerlinNoiseShaderContext::noise2D(
|
||||
checkNoise(noiseX.noisePositionIntegerValue, stitchData.fWrapX, stitchData.fWidth);
|
||||
noiseY.noisePositionIntegerValue =
|
||||
checkNoise(noiseY.noisePositionIntegerValue, stitchData.fWrapY, stitchData.fHeight);
|
||||
noiseX.nextNoisePositionIntegerValue =
|
||||
checkNoise(noiseX.nextNoisePositionIntegerValue, stitchData.fWrapX, stitchData.fWidth);
|
||||
noiseY.nextNoisePositionIntegerValue =
|
||||
checkNoise(noiseY.nextNoisePositionIntegerValue, stitchData.fWrapY, stitchData.fHeight);
|
||||
}
|
||||
noiseX.noisePositionIntegerValue &= kBlockMask;
|
||||
noiseY.noisePositionIntegerValue &= kBlockMask;
|
||||
int latticeIndex =
|
||||
paintingData.fLatticeSelector[noiseX.noisePositionIntegerValue] +
|
||||
noiseY.noisePositionIntegerValue;
|
||||
int nextLatticeIndex =
|
||||
paintingData.fLatticeSelector[(noiseX.noisePositionIntegerValue + 1) & kBlockMask] +
|
||||
noiseY.noisePositionIntegerValue;
|
||||
noiseX.nextNoisePositionIntegerValue &= kBlockMask;
|
||||
noiseY.nextNoisePositionIntegerValue &= kBlockMask;
|
||||
int i =
|
||||
paintingData.fLatticeSelector[noiseX.noisePositionIntegerValue];
|
||||
int j =
|
||||
paintingData.fLatticeSelector[noiseX.nextNoisePositionIntegerValue];
|
||||
int b00 = (i + noiseY.noisePositionIntegerValue) & kBlockMask;
|
||||
int b10 = (j + noiseY.noisePositionIntegerValue) & kBlockMask;
|
||||
int b01 = (i + noiseY.nextNoisePositionIntegerValue) & kBlockMask;
|
||||
int b11 = (j + noiseY.nextNoisePositionIntegerValue) & kBlockMask;
|
||||
SkScalar sx = smoothCurve(noiseX.noisePositionFractionValue);
|
||||
SkScalar sy = smoothCurve(noiseY.noisePositionFractionValue);
|
||||
// This is taken 1:1 from SVG spec: http://www.w3.org/TR/SVG11/filters.html#feTurbulenceElement
|
||||
SkPoint fractionValue = SkPoint::Make(noiseX.noisePositionFractionValue,
|
||||
noiseY.noisePositionFractionValue); // Offset (0,0)
|
||||
u = paintingData.fGradient[channel][latticeIndex & kBlockMask].dot(fractionValue);
|
||||
u = paintingData.fGradient[channel][b00].dot(fractionValue);
|
||||
fractionValue.fX -= SK_Scalar1; // Offset (-1,0)
|
||||
v = paintingData.fGradient[channel][nextLatticeIndex & kBlockMask].dot(fractionValue);
|
||||
v = paintingData.fGradient[channel][b10].dot(fractionValue);
|
||||
SkScalar a = SkScalarInterp(u, v, sx);
|
||||
fractionValue.fY -= SK_Scalar1; // Offset (-1,-1)
|
||||
v = paintingData.fGradient[channel][(nextLatticeIndex + 1) & kBlockMask].dot(fractionValue);
|
||||
v = paintingData.fGradient[channel][b11].dot(fractionValue);
|
||||
fractionValue.fX = noiseX.noisePositionFractionValue; // Offset (0,-1)
|
||||
u = paintingData.fGradient[channel][(latticeIndex + 1) & kBlockMask].dot(fractionValue);
|
||||
u = paintingData.fGradient[channel][b01].dot(fractionValue);
|
||||
SkScalar b = SkScalarInterp(u, v, sx);
|
||||
return SkScalarInterp(a, b, sy);
|
||||
}
|
||||
@ -989,13 +996,14 @@ void GrGLPerlinNoise::emitCode(GrGLShaderBuilder* builder,
|
||||
const char* chanCoord = "chanCoord";
|
||||
const char* stitchData = "stitchData";
|
||||
const char* ratio = "ratio";
|
||||
const char* noiseXY = "noiseXY";
|
||||
const char* noiseVec = "noiseVec";
|
||||
const char* noiseSmooth = "noiseSmooth";
|
||||
const char* floorVal = "floorVal";
|
||||
const char* fractVal = "fractVal";
|
||||
const char* uv = "uv";
|
||||
const char* ab = "ab";
|
||||
const char* latticeIdx = "latticeIdx";
|
||||
const char* bcoords = "bcoords";
|
||||
const char* lattice = "lattice";
|
||||
const char* inc8bit = "0.00390625"; // 1.0 / 256.0
|
||||
// This is the math to convert the two 16bit integer packed into rgba 8 bit input into a
|
||||
@ -1016,32 +1024,35 @@ void GrGLPerlinNoise::emitCode(GrGLShaderBuilder* builder,
|
||||
|
||||
SkString noiseCode;
|
||||
|
||||
noiseCode.appendf("\tvec4 %s = vec4(floor(%s), fract(%s));", noiseXY, noiseVec, noiseVec);
|
||||
noiseCode.appendf("\tvec4 %s;\n", floorVal);
|
||||
noiseCode.appendf("\t%s.xy = floor(%s);\n", floorVal, noiseVec);
|
||||
noiseCode.appendf("\t%s.zw = %s.xy + vec2(1.0);\n", floorVal, floorVal);
|
||||
noiseCode.appendf("\tvec2 %s = fract(%s);\n", fractVal, noiseVec);
|
||||
|
||||
// smooth curve : t * t * (3 - 2 * t)
|
||||
noiseCode.appendf("\n\tvec2 %s = %s.zw * %s.zw * (vec2(3.0) - vec2(2.0) * %s.zw);",
|
||||
noiseSmooth, noiseXY, noiseXY, noiseXY);
|
||||
noiseCode.appendf("\n\tvec2 %s = %s * %s * (vec2(3.0) - vec2(2.0) * %s);",
|
||||
noiseSmooth, fractVal, fractVal, fractVal);
|
||||
|
||||
// Adjust frequencies if we're stitching tiles
|
||||
if (fStitchTiles) {
|
||||
noiseCode.appendf("\n\tif(%s.x >= %s.x) { %s.x -= %s.x; }",
|
||||
noiseXY, stitchData, noiseXY, stitchData);
|
||||
noiseCode.appendf("\n\tif(%s.x >= (%s.x - 1.0)) { %s.x -= (%s.x - 1.0); }",
|
||||
noiseXY, stitchData, noiseXY, stitchData);
|
||||
floorVal, stitchData, floorVal, stitchData);
|
||||
noiseCode.appendf("\n\tif(%s.y >= %s.y) { %s.y -= %s.y; }",
|
||||
noiseXY, stitchData, noiseXY, stitchData);
|
||||
noiseCode.appendf("\n\tif(%s.y >= (%s.y - 1.0)) { %s.y -= (%s.y - 1.0); }",
|
||||
noiseXY, stitchData, noiseXY, stitchData);
|
||||
floorVal, stitchData, floorVal, stitchData);
|
||||
noiseCode.appendf("\n\tif(%s.z >= %s.x) { %s.z -= %s.x; }",
|
||||
floorVal, stitchData, floorVal, stitchData);
|
||||
noiseCode.appendf("\n\tif(%s.w >= %s.y) { %s.w -= %s.y; }",
|
||||
floorVal, stitchData, floorVal, stitchData);
|
||||
}
|
||||
|
||||
// Get texture coordinates and normalize
|
||||
noiseCode.appendf("\n\t%s.xy = fract(floor(mod(%s.xy, 256.0)) / vec2(256.0));\n",
|
||||
noiseXY, noiseXY);
|
||||
noiseCode.appendf("\n\t%s = fract(floor(mod(%s, 256.0)) / vec4(256.0));\n",
|
||||
floorVal, floorVal);
|
||||
|
||||
// Get permutation for x
|
||||
{
|
||||
SkString xCoords("");
|
||||
xCoords.appendf("vec2(%s.x, 0.5)", noiseXY);
|
||||
xCoords.appendf("vec2(%s.x, 0.5)", floorVal);
|
||||
|
||||
noiseCode.appendf("\n\tvec2 %s;\n\t%s.x = ", latticeIdx, latticeIdx);
|
||||
builder->appendTextureLookup(&noiseCode, samplers[0], xCoords.c_str(), kVec2f_GrSLType);
|
||||
@ -1051,7 +1062,7 @@ void GrGLPerlinNoise::emitCode(GrGLShaderBuilder* builder,
|
||||
// Get permutation for x + 1
|
||||
{
|
||||
SkString xCoords("");
|
||||
xCoords.appendf("vec2(fract(%s.x + %s), 0.5)", noiseXY, inc8bit);
|
||||
xCoords.appendf("vec2(%s.z, 0.5)", floorVal);
|
||||
|
||||
noiseCode.appendf("\n\t%s.y = ", latticeIdx);
|
||||
builder->appendTextureLookup(&noiseCode, samplers[0], xCoords.c_str(), kVec2f_GrSLType);
|
||||
@ -1070,15 +1081,13 @@ void GrGLPerlinNoise::emitCode(GrGLShaderBuilder* builder,
|
||||
#endif
|
||||
|
||||
// Get (x,y) coordinates with the permutated x
|
||||
noiseCode.appendf("\n\t%s = fract(%s + %s.yy);", latticeIdx, latticeIdx, noiseXY);
|
||||
|
||||
noiseCode.appendf("\n\tvec2 %s = %s.zw;", fractVal, noiseXY);
|
||||
noiseCode.appendf("\n\tvec4 %s = fract(%s.xyxy + %s.yyww);", bcoords, latticeIdx, floorVal);
|
||||
|
||||
noiseCode.appendf("\n\n\tvec2 %s;", uv);
|
||||
// Compute u, at offset (0,0)
|
||||
{
|
||||
SkString latticeCoords("");
|
||||
latticeCoords.appendf("vec2(%s.x, %s)", latticeIdx, chanCoord);
|
||||
latticeCoords.appendf("vec2(%s.x, %s)", bcoords, chanCoord);
|
||||
noiseCode.appendf("\n\tvec4 %s = ", lattice);
|
||||
builder->appendTextureLookup(&noiseCode, samplers[1], latticeCoords.c_str(),
|
||||
kVec2f_GrSLType);
|
||||
@ -1090,7 +1099,7 @@ void GrGLPerlinNoise::emitCode(GrGLShaderBuilder* builder,
|
||||
// Compute v, at offset (-1,0)
|
||||
{
|
||||
SkString latticeCoords("");
|
||||
latticeCoords.appendf("vec2(%s.y, %s)", latticeIdx, chanCoord);
|
||||
latticeCoords.appendf("vec2(%s.y, %s)", bcoords, chanCoord);
|
||||
noiseCode.append("\n\tlattice = ");
|
||||
builder->appendTextureLookup(&noiseCode, samplers[1], latticeCoords.c_str(),
|
||||
kVec2f_GrSLType);
|
||||
@ -1106,7 +1115,7 @@ void GrGLPerlinNoise::emitCode(GrGLShaderBuilder* builder,
|
||||
// Compute v, at offset (-1,-1)
|
||||
{
|
||||
SkString latticeCoords("");
|
||||
latticeCoords.appendf("vec2(fract(%s.y + %s), %s)", latticeIdx, inc8bit, chanCoord);
|
||||
latticeCoords.appendf("vec2(%s.w, %s)", bcoords, chanCoord);
|
||||
noiseCode.append("\n\tlattice = ");
|
||||
builder->appendTextureLookup(&noiseCode, samplers[1], latticeCoords.c_str(),
|
||||
kVec2f_GrSLType);
|
||||
@ -1118,7 +1127,7 @@ void GrGLPerlinNoise::emitCode(GrGLShaderBuilder* builder,
|
||||
// Compute u, at offset (0,-1)
|
||||
{
|
||||
SkString latticeCoords("");
|
||||
latticeCoords.appendf("vec2(fract(%s.x + %s), %s)", latticeIdx, inc8bit, chanCoord);
|
||||
latticeCoords.appendf("vec2(%s.z, %s)", bcoords, chanCoord);
|
||||
noiseCode.append("\n\tlattice = ");
|
||||
builder->appendTextureLookup(&noiseCode, samplers[1], latticeCoords.c_str(),
|
||||
kVec2f_GrSLType);
|
||||
|
Loading…
Reference in New Issue
Block a user