From ee3216d8f8358d8cbb43427b4f421cca5ea1ad20 Mon Sep 17 00:00:00 2001 From: Mike Reed Date: Fri, 17 Jan 2020 17:35:04 -0500 Subject: [PATCH] Use SkSL to do point-light shading - misc fixes to utilities - hit-testing for 3D scenes (simple version) Had to manually inform the shader of the local-to-world matrix. Should try making that automatic in the future. Note: due to bug in interpreter, point-light sample can't run in raster (yet). Change-Id: I7a30b7676ea6cd7eb264373dd2507133c901d85e Reviewed-on: https://skia-review.googlesource.com/c/skia/+/264999 Reviewed-by: Mike Reed Commit-Queue: Mike Reed --- include/core/SkCanvas.h | 1 + include/private/SkM44.h | 2 +- samplecode/Sample.cpp | 2 +- samplecode/Sample3D.cpp | 179 ++++++++++++++++++++++++++++++++++++++-- src/core/SkCanvas.cpp | 4 + 5 files changed, 180 insertions(+), 8 deletions(-) diff --git a/include/core/SkCanvas.h b/include/core/SkCanvas.h index fee4cae2d0..79d74d9e3b 100644 --- a/include/core/SkCanvas.h +++ b/include/core/SkCanvas.h @@ -881,6 +881,7 @@ public: void concat(const SkMatrix& matrix); void experimental_concat44(const SkMatrix44&); + void experimental_concat44(const SkM44&); void experimental_concat44(const SkScalar[]); // column-major /** Replaces SkMatrix with matrix. diff --git a/include/private/SkM44.h b/include/private/SkM44.h index 2aa0110577..58216c6ac9 100644 --- a/include/private/SkM44.h +++ b/include/private/SkM44.h @@ -176,7 +176,7 @@ public: SkV4 map(float x, float y, float z, float w) const; SkV4 operator*(const SkV4& v) const { - return this->map(v.x, v.y, v.z, v.z); + return this->map(v.x, v.y, v.z, v.w); } SkV3 operator*(const SkV3& v) const { auto v4 = this->map(v.x, v.y, v.z, 0); diff --git a/samplecode/Sample.cpp b/samplecode/Sample.cpp index c2ba7883eb..3f92d82485 100644 --- a/samplecode/Sample.cpp +++ b/samplecode/Sample.cpp @@ -66,7 +66,7 @@ bool Sample::mouse(SkPoint point, skui::InputState clickState, skui::ModifierKey case skui::InputState::kDown: fClick = nullptr; if (point.x() < 0 || point.y() < 0 || point.x() >= fWidth || point.y() >= fHeight) { - return false; + // return false; } fClick.reset(this->onFindClickHandler(point.x(), point.y(), modifierKeys)); if (!fClick) { diff --git a/samplecode/Sample3D.cpp b/samplecode/Sample3D.cpp index 1410fb6975..161a3a2c86 100644 --- a/samplecode/Sample3D.cpp +++ b/samplecode/Sample3D.cpp @@ -21,6 +21,17 @@ static SkMatrix44 inv(const SkMatrix44& m) { return inverse; } +static SkM44 inv(const SkM44& m) { + SkM44 inverse; + SkAssertResult(m.invert(&inverse)); + return inverse; +} + +static SkPoint project(const SkM44& m, SkV4 p) { + auto v = m * p; + return {v.x / v.w, v.y / v.w}; +} + class Sample3DView : public Sample { protected: float fNear = 0.05f; @@ -57,6 +68,8 @@ public: viewport.setScale(area.width()*0.5f, area.height()*0.5f, zscale) .postTranslate(area.centerX(), area.centerY(), 0); + // want "world" to be in our big coordinates (e.g. area), so apply this inverse + // as part of our "camera". canvas->experimental_saveCamera(viewport * perspective, camera * inv(viewport)); } @@ -103,6 +116,7 @@ static SkMatrix44 RY(SkScalar rad) { struct Face { SkScalar fRx, fRy; + SkColor fColor; static SkMatrix44 T(SkScalar x, SkScalar y, SkScalar z) { SkMatrix44 m; @@ -134,14 +148,14 @@ static bool front(const SkM44& m) { } const Face faces[] = { - { 0, 0 }, // front - { 0, SK_ScalarPI }, // back + { 0, 0, SK_ColorRED }, // front + { 0, SK_ScalarPI, SK_ColorGREEN }, // back - { SK_ScalarPI/2, 0 }, // top - {-SK_ScalarPI/2, 0 }, // bottom + { SK_ScalarPI/2, 0, SK_ColorBLUE }, // top + {-SK_ScalarPI/2, 0, SK_ColorCYAN }, // bottom - { 0, SK_ScalarPI/2 }, // left - { 0,-SK_ScalarPI/2 }, // right + { 0, SK_ScalarPI/2, SK_ColorMAGENTA }, // left + { 0,-SK_ScalarPI/2, SK_ColorYELLOW }, // right }; #include "include/core/SkColorFilter.h" @@ -272,3 +286,156 @@ class SampleRR3D : public Sample3DView { } }; DEF_SAMPLE( return new SampleRR3D(); ) + +#include "include/effects/SkRuntimeEffect.h" + +struct LightPos { + SkV4 fPos; + SkScalar fUIRadius; + + bool hitTest(SkScalar x, SkScalar y) const { + auto xx = x - fPos.x; + auto yy = y - fPos.y; + return xx*xx + yy*yy <= fUIRadius*fUIRadius; + } + + void update(SkScalar x, SkScalar y) { + fPos.x = x; + fPos.y = y; + } + + void draw(SkCanvas* canvas) { + SkPaint paint; + paint.setAntiAlias(true); + + SkAutoCanvasRestore acr(canvas, true); + canvas->experimental_concat44(SkM44::Translate(0, 0, fPos.z)); + canvas->drawCircle(fPos.x, fPos.y, fUIRadius, paint); + } +}; + +class SamplePointLight3D : public Sample3DView { + SkRRect fRR; + LightPos fLight = {{200, 200, 800, 1}, 8}; + + sk_sp fShader; + sk_sp fEffect; + + SkM44 fWorldToClick, + fClickToWorld; + + SkString name() override { return SkString("pointlight3d"); } + + void onOnceBeforeDraw() override { + fRR = SkRRect::MakeRectXY({20, 20, 380, 380}, 50, 50); + fShader = GetResourceAsImage("images/mandrill_128.png") + ->makeShader(SkMatrix::MakeScale(3, 3)); + + const char code[] = R"( + // in fragmentProcessor texture; + // color = sample(texture) * half(scale); + + uniform float4x4 localToWorld; + uniform float3 lightPos; + + void main(float x, float y, inout half4 color) { + float3 plane_pos = (localToWorld * float4(x, y, 0, 1)).xyz; + float3 plane_norm = normalize((localToWorld * float4(0, 0, 1, 0)).xyz); + float3 light_dir = normalize(lightPos - plane_pos); + float ambient = 0.5; + float dp = dot(plane_norm, light_dir); + float scale = ambient + max(dp, 0); + + color = color * half4(float4(scale, scale, scale, 1)); + } + )"; + auto [effect, error] = SkRuntimeEffect::Make(SkString(code)); + if (!effect) { + SkDebugf("runtime error %s\n", error.c_str()); + } + fEffect = effect; + } + + bool onChar(SkUnichar uni) override { + switch (uni) { + case 'X': fLight.fPos.x += 10; return true; + case 'x': fLight.fPos.x -= 10; return true; + case 'Y': fLight.fPos.y += 10; return true; + case 'y': fLight.fPos.y -= 10; return true; + case 'Z': fLight.fPos.z += 10; return true; + case 'z': fLight.fPos.z -= 10; return true; + } + return this->Sample3DView::onChar(uni); + } + + void drawContent(SkCanvas* canvas, const SkMatrix44& m, SkColor color) { + SkMatrix44 trans; + trans.setTranslate(200, 200, 0); // center of the rotation + + canvas->experimental_concat44(trans * fRot * m * inv(trans)); + + // wonder if the runtimeeffect can do this reject? (in a setup function) + if (!front(canvas->experimental_getLocalToDevice())) { + return; + } + + struct Uniforms { + SkM44 fLocalToWorld; + SkV3 fLightPos; + } uni; + uni.fLocalToWorld = canvas->experimental_getLocalToWorld(); + uni.fLightPos = {fLight.fPos.x, fLight.fPos.y, fLight.fPos.z}; + sk_sp data = SkData::MakeWithCopy(&uni, sizeof(uni)); + + SkPaint paint; + paint.setColor(color); + paint.setShader(fEffect->makeShader(data, &fShader, 0, nullptr, true)); + + canvas->drawRRect(fRR, paint); + } + + void setClickToWorld(SkCanvas* canvas, const SkM44& clickM) { + auto l2d = canvas->experimental_getLocalToDevice(); + fWorldToClick = inv(clickM) * l2d; + fClickToWorld = inv(fWorldToClick); + } + + void onDrawContent(SkCanvas* canvas) override { + if (canvas->getGrContext() == nullptr) { + return; + } + SkM44 clickM = canvas->experimental_getLocalToDevice(); + + canvas->save(); + canvas->translate(400, 300); + + this->saveCamera(canvas, {0, 0, 400, 400}, 200); + + this->setClickToWorld(canvas, clickM); + + for (auto f : faces) { + SkAutoCanvasRestore acr(canvas, true); + this->drawContent(canvas, f.asM44(200), f.fColor); + } + + fLight.draw(canvas); + canvas->restore(); + canvas->restore(); + } + + Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override { + auto L = fWorldToClick * fLight.fPos; + SkPoint c = project(fClickToWorld, {x, y, L.z/L.w, 1}); + if (fLight.hitTest(c.fX, c.fY)) { + return new Click(); + } + return nullptr; + } + bool onClick(Click* click) override { + auto L = fWorldToClick * fLight.fPos; + SkPoint c = project(fClickToWorld, {click->fCurr.fX, click->fCurr.fY, L.z/L.w, 1}); + fLight.update(c.fX, c.fY); + return true; + } +}; +DEF_SAMPLE( return new SamplePointLight3D(); ) diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp index f9d9621e82..e0310f8a0e 100644 --- a/src/core/SkCanvas.cpp +++ b/src/core/SkCanvas.cpp @@ -1520,6 +1520,10 @@ void SkCanvas::experimental_concat44(const SkMatrix44& m) { this->experimental_concat44(m.values()); } +void SkCanvas::experimental_concat44(const SkM44& m) { + this->experimental_concat44(m.asColMajor()); +} + void SkCanvas::internalSetMatrix(const SkMatrix& matrix) { fMCRec->fMatrix = matrix; fIsScaleTranslate = matrix.isScaleTranslate();