mirror of
https://github.com/lubosz/overte.git
synced 2025-04-24 02:53:43 +02:00
fixing ao
This commit is contained in:
parent
a5c91c0602
commit
b4e5b60656
8 changed files with 159 additions and 129 deletions
|
@ -487,7 +487,7 @@ const gpu::PipelinePointer& DebugAmbientOcclusion::getDebugPipeline() {
|
|||
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
||||
|
||||
state->setColorWriteMask(true, true, true, false);
|
||||
|
||||
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
|
||||
// Good to go add the brand new pipeline
|
||||
_debugPipeline = gpu::Pipeline::create(program, state);
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ public:
|
|||
float numSpiralTurns{ 7.0f }; // defining an angle span to distribute the samples ray directions
|
||||
int numSamples{ 11 };
|
||||
int resolutionLevel{ 0 };
|
||||
int blurRadius{ 0 }; // 0 means no blurring
|
||||
int blurRadius{ 3 }; // 0 means no blurring
|
||||
bool ditheringEnabled{ true }; // randomize the distribution of rays per pixel, should always be true
|
||||
bool borderingEnabled{ true }; // avoid evaluating information from non existing pixels out of the frame, should always be true
|
||||
double gpuTime{ 0.0 };
|
||||
|
|
|
@ -85,6 +85,10 @@ float getStereoSideHeight(int resolutionLevel) {
|
|||
return float(int(frameTransform._pixelInfo.w) >> resolutionLevel);
|
||||
}
|
||||
|
||||
vec2 getSideImageSize(int resolutionLevel) {
|
||||
return vec2(float(int(frameTransform._stereoInfo.y) >> resolutionLevel), float(int(frameTransform._pixelInfo.w) >> resolutionLevel));
|
||||
}
|
||||
|
||||
ivec4 getStereoSideInfo(int xPos, int resolutionLevel) {
|
||||
int sideWidth = int(getStereoSideWidth(resolutionLevel));
|
||||
return ivec4(xPos < sideWidth ? ivec2(0, 0) : ivec2(1, sideWidth), sideWidth, isStereo());
|
||||
|
|
|
@ -178,14 +178,16 @@ RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) {
|
|||
|
||||
// Debugging stages
|
||||
{
|
||||
// Debugging Deferred buffer job
|
||||
const auto debugFramebuffers = render::Varying(DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer));
|
||||
addJob<DebugDeferredBuffer>("DebugDeferredBuffer", debugFramebuffers);
|
||||
|
||||
addJob<DebugSubsurfaceScattering>("DebugScattering", deferredLightingInputs);
|
||||
|
||||
const auto debugAmbientOcclusionInputs = DebugAmbientOcclusion::Inputs(deferredFrameTransform, deferredFramebuffer, linearDepthTarget, ambientOcclusionUniforms).hasVarying();
|
||||
addJob<DebugAmbientOcclusion>("DebugAmbientOcclusion", debugAmbientOcclusionInputs);
|
||||
|
||||
// Debugging Deferred buffer job
|
||||
const auto debugFramebuffers = render::Varying(DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer));
|
||||
addJob<DebugDeferredBuffer>("DebugDeferredBuffer", debugFramebuffers);
|
||||
|
||||
|
||||
// Scene Octree Debuging job
|
||||
{
|
||||
|
|
|
@ -126,6 +126,115 @@ float getBlurCoef(int c) {
|
|||
|
||||
<@endfunc@>
|
||||
|
||||
<@func declareSamplingDisk()@>
|
||||
|
||||
float evalDiskRadius(float Zeye, vec2 imageSize) {
|
||||
// Choose the screen-space sample radius
|
||||
// proportional to the projected area of the sphere
|
||||
float ssDiskRadius = -( getProjScale(getResolutionLevel()) * getRadius() / Zeye ) * getPerspectiveScale();
|
||||
|
||||
// clamp the disk to fit in the image otherwise too many unknown
|
||||
ssDiskRadius = min(ssDiskRadius, imageSize.y * 0.5);
|
||||
|
||||
return ssDiskRadius;
|
||||
}
|
||||
|
||||
const float TWO_PI = 6.28;
|
||||
|
||||
vec3 getUnitTapLocation(int sampleNumber, float spinAngle){
|
||||
// Radius relative to ssR
|
||||
float alpha = float(sampleNumber + 0.5) * getInvNumSamples();
|
||||
float angle = alpha * (getNumSpiralTurns() * TWO_PI) + spinAngle;
|
||||
return vec3(cos(angle), sin(angle), alpha);
|
||||
}
|
||||
|
||||
vec3 getTapLocation(int sampleNumber, float spinAngle, float outerRadius) {
|
||||
vec3 tap = getUnitTapLocation(sampleNumber, spinAngle);
|
||||
tap.xy *= tap.z;
|
||||
tap *= outerRadius;
|
||||
return tap;
|
||||
}
|
||||
|
||||
vec3 getTapLocationClamped(int sampleNumber, float spinAngle, float outerRadius, vec2 pixelPos, vec2 imageSize) {
|
||||
vec3 tap = getTapLocation(sampleNumber, spinAngle, outerRadius);
|
||||
vec2 tapPos = pixelPos + tap.xy;
|
||||
|
||||
if (!(isBorderingEnabled() > 0.0)) {
|
||||
return tap;
|
||||
}
|
||||
bool redoTap = false;
|
||||
|
||||
if ((tapPos.x < 0.5)) {
|
||||
tapPos.x = -tapPos.x;
|
||||
redoTap = true;
|
||||
} else if ((tapPos.x > imageSize.x - 0.5)) {
|
||||
tapPos.x -= (imageSize.x - tapPos.x);
|
||||
redoTap = true;
|
||||
}
|
||||
|
||||
if ((tapPos.y < 0.5)) {
|
||||
tapPos.y = -tapPos.y;
|
||||
redoTap = true;
|
||||
} else if ((tapPos.y > imageSize.y - 0.5)) {
|
||||
tapPos.y -= (imageSize.y - tapPos.y);
|
||||
redoTap = true;
|
||||
}
|
||||
|
||||
if (redoTap) {
|
||||
tap.xy = tapPos - pixelPos;
|
||||
tap.z = length(tap.xy);
|
||||
}
|
||||
/*
|
||||
if ((tapPos.x < 0.0) || (tapPos.x >= imageSize.x)) {
|
||||
// tapPos.x = pixelPos.x - tapVec.x;
|
||||
tap.x = -tap.x;
|
||||
}
|
||||
if ((tapPos.y < 0.0) || (tapPos.y >= imageSize.y)) {
|
||||
// tapPos.y = pixelPos.y - tapVec.y;
|
||||
tap.y = -tap.y;
|
||||
}*/
|
||||
|
||||
return tap;
|
||||
}
|
||||
|
||||
<@endfunc@>
|
||||
|
||||
|
||||
<@func declareFetchDepthPyramidMap()@>
|
||||
|
||||
const int LOG_MAX_OFFSET = 3;
|
||||
const int MAX_MIP_LEVEL = 5;
|
||||
|
||||
// the depth pyramid texture
|
||||
uniform sampler2D pyramidMap;
|
||||
|
||||
float getZEye(ivec2 pixel) {
|
||||
return -texelFetch(pyramidMap, pixel, getResolutionLevel()).x;
|
||||
}
|
||||
|
||||
vec3 getOffsetPosition(ivec3 side, ivec2 ssC, vec3 tap, vec2 imageSize) {
|
||||
// Derivation:
|
||||
// mipLevel = floor(log(ssR / MAX_OFFSET));
|
||||
int mipLevel = clamp(findMSB(int(tap.z)) - LOG_MAX_OFFSET, 0, MAX_MIP_LEVEL);
|
||||
|
||||
ivec2 ssP = ivec2(tap.xy) + ssC;
|
||||
ivec2 ssPFull = ivec2(ssP.x + side.y, ssP.y);
|
||||
|
||||
vec3 P;
|
||||
|
||||
// We need to divide by 2^mipLevel to read the appropriately scaled coordinate from a MIP-map.
|
||||
// Manually clamp to the texture size because texelFetch bypasses the texture unit
|
||||
ivec2 mipP = clamp(ssPFull >> mipLevel, ivec2(0), textureSize(pyramidMap, getResolutionLevel() + mipLevel) - ivec2(1));
|
||||
P.z = -texelFetch(pyramidMap, mipP, getResolutionLevel() + mipLevel).r;
|
||||
|
||||
// Offset to pixel center
|
||||
vec2 tapUV = (vec2(ssP) + vec2(0.5)) / imageSize;
|
||||
P = evalEyePositionFromZeye(side.x, P.z, tapUV);
|
||||
return P;
|
||||
}
|
||||
|
||||
<@endfunc@>
|
||||
|
||||
<@func declareBlurPass(axis)@>
|
||||
|
||||
<$declarePackOcclusionDepth()$>
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
|
||||
<@include ssao.slh@>
|
||||
<$declareAmbientOcclusion()$>
|
||||
<$declareFetchDepthPyramidMap()$>
|
||||
<$declareSamplingDisk()$>
|
||||
|
||||
<$declarePackOcclusionDepth()$>
|
||||
|
||||
struct DebugParams{
|
||||
|
@ -24,15 +27,6 @@ uniform debugAmbientOcclusionBuffer {
|
|||
vec2 getDebugCursorTexcoord(){
|
||||
return debugParams.pixelInfo.xy;
|
||||
}
|
||||
const int LOG_MAX_OFFSET = 3;
|
||||
const int MAX_MIP_LEVEL = 5;
|
||||
|
||||
// the depth pyramid texture
|
||||
uniform sampler2D pyramidMap;
|
||||
|
||||
float getZEye(ivec2 pixel) {
|
||||
return -texelFetch(pyramidMap, pixel, getResolutionLevel()).x;
|
||||
}
|
||||
|
||||
out vec4 outFragColor;
|
||||
|
||||
|
@ -43,63 +37,7 @@ float getAngleDithering(in ivec2 pixelPos) {
|
|||
return isDitheringEnabled() * (3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) * 10 + getFrameDithering();
|
||||
}
|
||||
|
||||
const float TWO_PI = 6.28;
|
||||
|
||||
vec2 tapLocation(int sampleNumber, float spinAngle, out float ssR){
|
||||
// Radius relative to ssR
|
||||
float alpha = float(sampleNumber + 0.5) * getInvNumSamples();
|
||||
float angle = alpha * (getNumSpiralTurns() * TWO_PI) + spinAngle;
|
||||
|
||||
ssR = alpha;
|
||||
return vec2(cos(angle), sin(angle));
|
||||
}
|
||||
|
||||
vec3 getTapLocation(int sampleNumber, float spinAngle, float outerRadius) {
|
||||
// Radius relative to ssR
|
||||
float alpha = float(sampleNumber + 0.5) * getInvNumSamples();
|
||||
float angle = alpha * (getNumSpiralTurns() * TWO_PI) + spinAngle;
|
||||
return vec2(cos(angle), sin(angle), alpha * outerRadius);
|
||||
}
|
||||
|
||||
vec3 getOffsetPosition(ivec3 side, ivec2 ssC, vec2 unitOffset, float ssR) {
|
||||
// Derivation:
|
||||
// mipLevel = floor(log(ssR / MAX_OFFSET));
|
||||
int mipLevel = clamp(findMSB(int(ssR)) - LOG_MAX_OFFSET, 0, MAX_MIP_LEVEL);
|
||||
|
||||
ivec2 ssOffset = ivec2(ssR * unitOffset);
|
||||
ivec2 ssP = ssOffset + ssC;
|
||||
if (bool(isBorderingEnabled())) {
|
||||
ssP.x = ((ssP.x < 0 || ssP.x >= side.z) ? ssC.x - ssOffset.x : ssP.x);
|
||||
ssP.y = ((ssP.y < 0 || ssP.y >= int(getWidthHeight(getResolutionLevel()).y)) ? ssC.y - ssOffset.y : ssP.y);
|
||||
}
|
||||
|
||||
ivec2 ssPFull = ivec2(ssP.x + side.y, ssP.y);
|
||||
|
||||
vec3 P;
|
||||
|
||||
// We need to divide by 2^mipLevel to read the appropriately scaled coordinate from a MIP-map.
|
||||
// Manually clamp to the texture size because texelFetch bypasses the texture unit
|
||||
ivec2 mipP = clamp(ssPFull >> mipLevel, ivec2(0), textureSize(pyramidMap, getResolutionLevel() + mipLevel) - ivec2(1));
|
||||
P.z = -texelFetch(pyramidMap, mipP, getResolutionLevel() + mipLevel).r;
|
||||
|
||||
// Offset to pixel center
|
||||
vec2 tapUV = (vec2(ssP) + vec2(0.5)) / float(side.z);
|
||||
P = evalEyePositionFromZeye(side.x, P.z, tapUV);
|
||||
return P;
|
||||
}
|
||||
|
||||
|
||||
float sampleAO(in ivec3 side, in ivec2 ssC, in vec3 C, in vec3 n_C, in float ssDiskRadius, in int tapIndex, in float randomPatternRotationAngle) {
|
||||
// Offset on the unit disk, spun for this pixel
|
||||
float ssR;
|
||||
vec2 unitOffset = tapLocation(tapIndex, randomPatternRotationAngle, ssR);
|
||||
ssR *= ssDiskRadius;
|
||||
|
||||
|
||||
|
||||
// The occluding point in camera space
|
||||
vec3 Q = getOffsetPosition(side, ssC, unitOffset, ssR);
|
||||
|
||||
float evalAO(in vec3 C, in vec3 n_C, vec3 Q) {
|
||||
vec3 v = Q - C;
|
||||
float vv = dot(v, v);
|
||||
float vn = dot(v, n_C);
|
||||
|
@ -114,7 +52,9 @@ float sampleAO(in ivec3 side, in ivec2 ssC, in vec3 C, in vec3 n_C, in float ssD
|
|||
void main(void) {
|
||||
// Pixel Debugged
|
||||
vec2 cursorUV = getDebugCursorTexcoord();
|
||||
ivec2 ssC = ivec2(cursorUV * vec2(getStereoSideWidth(getResolutionLevel()), getStereoSideHeight(getResolutionLevel())));
|
||||
vec2 imageSize = getSideImageSize(getResolutionLevel());
|
||||
vec2 cursorPixelPos = cursorUV * imageSize;
|
||||
ivec2 ssC = ivec2(cursorPixelPos);
|
||||
// Pixel being shaded
|
||||
//ivec2 ssC = ivec2(gl_FragCoord.xy);
|
||||
|
||||
|
@ -126,17 +66,16 @@ void main(void) {
|
|||
|
||||
// From now on, ssC is the pixel pos in the side
|
||||
ssC.x -= side.y;
|
||||
vec2 fragPos = (vec2(ssC) + vec2(0.5)) / vec2(getStereoSideWidth(getResolutionLevel()), getStereoSideHeight(getResolutionLevel()));
|
||||
vec2 fragPos = (vec2(ssC) + vec2(0.5)) / imageSize;
|
||||
|
||||
// The position and normal of the pixel fragment in Eye space
|
||||
vec3 Cp = evalEyePositionFromZeye(side.x, Zeye, fragPos);
|
||||
vec3 Cn = evalEyeNormal(Cp);
|
||||
|
||||
// Choose the screen-space sample radius
|
||||
// proportional to the projected area of the sphere
|
||||
float ssDiskRadius = -( getProjScale(getResolutionLevel()) * getRadius() / Cp.z ) * getPerspectiveScale();
|
||||
float ssDiskRadius = evalDiskRadius(Cp.z, imageSize);
|
||||
|
||||
vec2 fragToCursor = cursorUV * vec2(getStereoSideWidth(getResolutionLevel()), getStereoSideHeight(getResolutionLevel())) - gl_FragCoord.xy;
|
||||
vec2 fragToCursor = cursorPixelPos - gl_FragCoord.xy;
|
||||
if (dot(fragToCursor,fragToCursor) > ssDiskRadius * ssDiskRadius) {
|
||||
discard;
|
||||
}
|
||||
|
@ -149,18 +88,18 @@ void main(void) {
|
|||
float keepTapRadius = 2.0;
|
||||
bool keep = (dot(fragToCursor,fragToCursor) < keepTapRadius);
|
||||
for (int i = 0; i < getNumSamples(); ++i) {
|
||||
vec3 tap = getTapLocation(i, randomPatternRotationAngle, outerRadius);
|
||||
|
||||
vec2 fragToTap = vec2(ssC) + tap.xy * tap.z - gl_FragCoord.xy;
|
||||
vec3 tap = getTapLocationClamped(i, randomPatternRotationAngle, ssDiskRadius, cursorPixelPos, imageSize);
|
||||
// The occluding point in camera space
|
||||
vec2 fragToTap = vec2(ssC) + tap.xy - gl_FragCoord.xy;
|
||||
if (dot(fragToTap,fragToTap) < keepTapRadius) {
|
||||
keep = true;
|
||||
}
|
||||
sum += sampleAO(side.xyz, ssC, Cp, Cn, ssDiskRadius, i, randomPatternRotationAngle);
|
||||
|
||||
vec3 Q = getOffsetPosition(side.xyz, ssC, tap, imageSize);
|
||||
|
||||
sum += evalAO(Cp, Cn, Q);
|
||||
}
|
||||
|
||||
if (!keep) {
|
||||
discard;
|
||||
}
|
||||
|
||||
float A = max(0.0, 1.0 - sum * getObscuranceScaling() * 5.0 * getInvNumSamples());
|
||||
|
||||
|
@ -190,4 +129,9 @@ void main(void) {
|
|||
//outFragColor = vec4((v + vec3(1.0))* 0.5, 1.0);
|
||||
// outFragColor = vec4((Cn + vec3(1.0))* 0.5, 1.0);
|
||||
//outFragColor = vec4(vec3(ssDiskRadius / 100.0), 1.0);
|
||||
|
||||
|
||||
if (!keep) {
|
||||
outFragColor = vec4(0.1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,27 +11,12 @@
|
|||
|
||||
<@include ssao.slh@>
|
||||
<$declareAmbientOcclusion()$>
|
||||
<$declareFetchDepthPyramidMap()$>
|
||||
<$declareSamplingDisk()$>
|
||||
|
||||
<$declarePackOcclusionDepth()$>
|
||||
|
||||
|
||||
const int LOG_MAX_OFFSET = 3;
|
||||
const int MAX_MIP_LEVEL = 5;
|
||||
|
||||
// the depth pyramid texture
|
||||
uniform sampler2D pyramidMap;
|
||||
|
||||
float getZEye(ivec2 pixel) {
|
||||
return -texelFetch(pyramidMap, pixel, getResolutionLevel()).x;
|
||||
}
|
||||
/*
|
||||
vec3 evalEyePositionFromZeye(int side, float Zeye, vec2 texcoord) {
|
||||
// compute the view space position using the depth
|
||||
// basically manually pick the proj matrix components to do the inverse
|
||||
float Xe = (-Zeye * (texcoord.x * 2.0 - 1.0) - Zeye * frameTransform._projection[side][2][0] - frameTransform._projection[side][3][0]) / frameTransform._projection[side][0][0];
|
||||
float Ye = (-Zeye * (texcoord.y * 2.0 - 1.0) - Zeye * frameTransform._projection[side][2][1] - frameTransform._projection[side][3][1]) / frameTransform._projection[side][1][1];
|
||||
return vec3(Xe, Ye, Zeye);
|
||||
}
|
||||
*/
|
||||
out vec4 outFragColor;
|
||||
|
||||
uniform sampler2D normalMap;
|
||||
|
@ -40,18 +25,7 @@ float getAngleDithering(in ivec2 pixelPos) {
|
|||
// Hash function used in the AlchemyAO paper
|
||||
return isDitheringEnabled() * (3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) * 10 + getFrameDithering();
|
||||
}
|
||||
|
||||
const float TWO_PI = 6.28;
|
||||
|
||||
vec2 tapLocation(int sampleNumber, float spinAngle, out float ssR){
|
||||
// Radius relative to ssR
|
||||
float alpha = float(sampleNumber + 0.5) * getInvNumSamples();
|
||||
float angle = alpha * (getNumSpiralTurns() * TWO_PI) + spinAngle;
|
||||
|
||||
ssR = alpha;
|
||||
return vec2(cos(angle), sin(angle));
|
||||
}
|
||||
|
||||
/*
|
||||
vec3 getOffsetPosition(ivec3 side, ivec2 ssC, vec2 unitOffset, float ssR) {
|
||||
// Derivation:
|
||||
// mipLevel = floor(log(ssR / MAX_OFFSET));
|
||||
|
@ -77,19 +51,9 @@ vec3 getOffsetPosition(ivec3 side, ivec2 ssC, vec2 unitOffset, float ssR) {
|
|||
vec2 tapUV = (vec2(ssP) + vec2(0.5)) / float(side.z);
|
||||
P = evalEyePositionFromZeye(side.x, P.z, tapUV);
|
||||
return P;
|
||||
}
|
||||
|
||||
float sampleAO(in ivec3 side, in ivec2 ssC, in vec3 C, in vec3 n_C, in float ssDiskRadius, in int tapIndex, in float randomPatternRotationAngle) {
|
||||
// Offset on the unit disk, spun for this pixel
|
||||
float ssR;
|
||||
vec2 unitOffset = tapLocation(tapIndex, randomPatternRotationAngle, ssR);
|
||||
ssR *= ssDiskRadius;
|
||||
|
||||
|
||||
|
||||
// The occluding point in camera space
|
||||
vec3 Q = getOffsetPosition(side, ssC, unitOffset, ssR);
|
||||
}*/
|
||||
|
||||
float evalAO(in vec3 C, in vec3 n_C, in vec3 Q) {
|
||||
vec3 v = Q - C;
|
||||
float vv = dot(v, v);
|
||||
float vn = dot(v, n_C);
|
||||
|
@ -104,6 +68,8 @@ void main(void) {
|
|||
// Pixel being shaded
|
||||
ivec2 ssC = ivec2(gl_FragCoord.xy);
|
||||
|
||||
vec2 imageSize = getSideImageSize(getResolutionLevel());
|
||||
|
||||
// Fetch the z under the pixel (stereo or not)
|
||||
float Zeye = getZEye(ssC);
|
||||
|
||||
|
@ -119,8 +85,7 @@ void main(void) {
|
|||
vec3 Cn = evalEyeNormal(Cp);
|
||||
|
||||
// Choose the screen-space sample radius
|
||||
// proportional to the projected area of the sphere
|
||||
float ssDiskRadius = -( getProjScale(getResolutionLevel()) * getRadius() / Cp.z ) * getPerspectiveScale();
|
||||
float ssDiskRadius = evalDiskRadius(Cp.z, imageSize);
|
||||
|
||||
// Let's make noise
|
||||
float randomPatternRotationAngle = getAngleDithering(ssC);
|
||||
|
@ -128,7 +93,11 @@ void main(void) {
|
|||
// Accumulate the Obscurance for each samples
|
||||
float sum = 0.0;
|
||||
for (int i = 0; i < getNumSamples(); ++i) {
|
||||
sum += sampleAO(side.xyz, ssC, Cp, Cn, ssDiskRadius, i, randomPatternRotationAngle);
|
||||
vec3 tap = getTapLocationClamped(i, randomPatternRotationAngle, ssDiskRadius, ssC, imageSize);
|
||||
|
||||
vec3 Q = getOffsetPosition(side.xyz, ssC, tap, imageSize);
|
||||
|
||||
sum += evalAO(Cp, Cn, Q);
|
||||
}
|
||||
|
||||
float A = max(0.0, 1.0 - sum * getObscuranceScaling() * 5.0 * getInvNumSamples());
|
||||
|
|
|
@ -22,7 +22,9 @@ Column {
|
|||
model: [
|
||||
"Radius:radius:2.0:false",
|
||||
"Level:obscuranceLevel:1.0:false",
|
||||
"Scale:perspectiveScale:2.0:false",
|
||||
"Num Taps:numSamples:32:true",
|
||||
"Taps Spiral:numSpiralTurns:10.0:false",
|
||||
"Blur Radius:blurRadius:10.0:false",
|
||||
]
|
||||
ConfigSlider {
|
||||
label: qsTr(modelData.split(":")[0])
|
||||
|
|
Loading…
Reference in a new issue