summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiikka Heikkinen <miikka.heikkinen@digia.com>2014-08-28 15:42:52 +0300
committerMiikka Heikkinen <miikka.heikkinen@digia.com>2014-09-01 11:05:13 +0300
commita20806dac74415f3d8cb6679c9eae86ce074ddae (patch)
tree49bffc538f0dac3feb99e23787b7e945a4a554d4
parentcd1a66a8fcdcc6870656a924bf55ffbcd6ea1162 (diff)
Pixel perfect volume shader
Now the volume should render with pixel perfect accuracy. The drawback is that it is significantly slower than the old approximate solution. Change-Id: I4df7e9a28a27b56150c71a85a4c1fb69a215dabc Reviewed-by: Tomi Korpipää <tomi.korpipaa@digia.com>
-rw-r--r--src/datavisualization/engine/abstract3drenderer.cpp12
-rw-r--r--src/datavisualization/engine/shaders/texture3d.frag162
-rw-r--r--src/datavisualization/engine/shaders/texture3dslice.frag29
3 files changed, 128 insertions, 75 deletions
diff --git a/src/datavisualization/engine/abstract3drenderer.cpp b/src/datavisualization/engine/abstract3drenderer.cpp
index 090a833a..78f4b600 100644
--- a/src/datavisualization/engine/abstract3drenderer.cpp
+++ b/src/datavisualization/engine/abstract3drenderer.cpp
@@ -1320,15 +1320,15 @@ void Abstract3DRenderer::drawCustomItems(RenderingState state,
} else {
// Precalculate texture dimensions so we can optimize
// ray stepping to hit every texture layer.
- QVector3D textureDimensions(float(item->textureWidth()),
- float(item->textureHeight()),
- float(item->textureDepth()));
+ QVector3D textureDimensions(1.0f / float(item->textureWidth()),
+ 1.0f / float(item->textureHeight()),
+ 1.0f / float(item->textureDepth()));
shader->setUniformValue(shader->textureDimensions(), textureDimensions);
- int sampleCount = qMax(item->textureWidth(), item->textureHeight());
- sampleCount = qMax(sampleCount, item->textureDepth());
+ // Worst case scenario sample count
+ int sampleCount = item->textureWidth() + item->textureHeight()
+ + item->textureDepth();
shader->setUniformValue(shader->sampleCount(), sampleCount);
-
}
m_drawer->drawObject(shader, item->mesh(), 0, 0, item->texture());
} else
diff --git a/src/datavisualization/engine/shaders/texture3d.frag b/src/datavisualization/engine/shaders/texture3d.frag
index 1192ae85..6dd7f78c 100644
--- a/src/datavisualization/engine/shaders/texture3d.frag
+++ b/src/datavisualization/engine/shaders/texture3d.frag
@@ -11,7 +11,10 @@ uniform highp int sampleCount; // This is the maximum sample count
uniform highp float alphaMultiplier;
uniform highp int preserveOpacity;
-const highp float alphaThreshold = 0.0001;
+// Ray traveling straight through a single 'alpha thickness' applies 100% of the encountered alpha.
+// Rays traveling shorter distances apply a fraction. This is used to normalize the alpha over
+// entire volume, regardless of texture dimensions
+const highp float alphaThicknesses = 32.0;
void main() {
highp vec3 rayDir = -(cameraPositionRelativeToModel - pos);
@@ -41,69 +44,122 @@ void main() {
if (rayDir.z < 0)
boxBounds.z = -1.0;
highp vec3 t = (boxBounds - rayStart) * invRayDir;
- highp float minT = min(t.x, t.y);
- minT = min(minT, t.z);
+ highp float minT = min(t.x, min(t.y, t.z));
rayStop = rayStart + minT * rayDir;
}
+ // Convert intersections to texture coords
+ rayStart = 0.5 * (rayStart + 1.0);
+ rayStop = 0.5 * (rayStop + 1.0);
+
+ highp vec3 ray = rayStop - rayStart;
+
+ // Avoid artifacts from divisions by zero
+ if (ray.x == 0)
+ ray.x = 0.000000001;
+ if (ray.y == 0)
+ ray.y = 0.000000001;
+ if (ray.z == 0)
+ ray.z = 0.000000001;
+
+ highp vec3 absRay = abs(ray);
+ highp vec3 invAbsRay = 1.0 / absRay;
+ highp float fullDist = length(ray);
+ highp vec3 curPos = rayStart;
+
+ highp vec4 curColor = vec4(0, 0, 0, 0);
+ highp float curAlpha = 0.0;
+ highp float curLen = 0.0;
+ highp vec3 curRgb = vec3(0, 0, 0);
+
highp vec4 destColor = vec4(0, 0, 0, 0);
- highp float totalAlpha = 0.0;
-
- if (rayStart != rayStop) {
- // Convert intersections to texture coords
- rayStart = 0.5 * (rayStart + 1.0);
- rayStop = 0.5 * (rayStop + 1.0);
-
- highp vec3 ray = rayStop - rayStart;
- highp float fullDist = abs(length(ray));
- highp float rayX = abs(ray.x) * textureDimensions.x;
- highp float rayY = abs(ray.y) * textureDimensions.y;
- highp float rayZ = abs(ray.z) * textureDimensions.z;
- highp float maxRayDim = max(rayX, rayY);
- maxRayDim = max(maxRayDim, rayZ);
- int maxCount = int(floor(maxRayDim));
-
- highp vec3 step = ray / maxRayDim;
- highp float stepSize = fullDist / maxRayDim;
-
- rayStart += (step * 0.001);
- highp vec3 curPos = rayStart;
-
- // Adjust alpha multiplier according to the step size to get uniform alpha effect
- // regardless of the ray angle.
- highp float totalAlphaMultiplier = (stepSize / (1.0 / sampleCount)) * alphaMultiplier;
-
- highp vec4 curColor = vec4(0, 0, 0, 0);
- highp vec3 curRgb = vec3(0, 0, 0);
- highp float curAlpha = 0.0;
-
- // Raytrace into volume, need to sample pixels along the eye ray until we hit opacity 1
- for (int i = 0; i < sampleCount; i++) {
- curColor = texture3D(textureSampler, curPos);
- if (color8Bit != 0)
- curColor = colorIndex[int(curColor.r * 255.0)];
-
- // Unless we have explicit alpha multiplier, we want to preserve opacity anyway
- if (curColor.a == 1.0 && (preserveOpacity != 0 || alphaMultiplier == 1.0))
+ highp float totalOpacity = 1.0;
+ highp float nextOpacity = 1.0;
+
+ highp float extraAlphaMultiplier = fullDist * alphaThicknesses;
+
+ // nextEdges vector indicates the next edges of the texel boundaries along each axis that
+ // the ray is about to cross. The first edges are offset by a fraction of a texel to
+ // avoid artifacts from rounding errors later.
+ highp vec3 nextEdges = vec3(floor(curPos.x / textureDimensions.x) * textureDimensions.x,
+ floor(curPos.y / textureDimensions.y) * textureDimensions.y,
+ floor(curPos.z / textureDimensions.z) * textureDimensions.z);
+
+ highp vec3 textureSteps = textureDimensions;
+ highp vec3 textureOffset = textureDimensions * 0.001;
+ if (ray.x > 0) {
+ nextEdges.x += textureDimensions.x + textureOffset.x;
+ } else {
+ nextEdges.x -= textureOffset.x;
+ textureSteps.x = -textureDimensions.x;
+ }
+ if (ray.y > 0) {
+ nextEdges.y += textureDimensions.y + textureOffset.y;
+ } else {
+ nextEdges.y -= textureOffset.y;
+ textureSteps.y = -textureDimensions.y;
+ }
+ if (ray.z > 0) {
+ nextEdges.z += textureDimensions.z + textureOffset.z;
+ } else {
+ nextEdges.z -= textureOffset.z;
+ textureSteps.z = -textureDimensions.z;
+ }
+
+ // Raytrace into volume, need to sample pixels along the eye ray until we hit opacity 1
+ for (int i = 0; i < sampleCount; i++) {
+ curColor = texture3D(textureSampler, curPos);
+ if (color8Bit != 0)
+ curColor = colorIndex[int(curColor.r * 255.0)];
+
+ // Find which dimension has least to go to figure out the next step distance
+ highp vec3 delta = abs(nextEdges - curPos);
+ highp vec3 modDelta = delta * invAbsRay;
+ highp float minDelta = min(modDelta.x, min(modDelta.y, modDelta.z));
+ highp float stepSize;
+ if (minDelta == modDelta.x) {
+ nextEdges.x += textureSteps.x;
+ stepSize = delta.x * invAbsRay.x;
+ }
+ if (minDelta == modDelta.y) {
+ nextEdges.y += textureSteps.y;
+ stepSize = delta.y * invAbsRay.y;
+ }
+ if (minDelta == modDelta.z) {
+ nextEdges.z += textureSteps.z;
+ stepSize = delta.z * invAbsRay.z;
+ }
+
+ curPos += stepSize * ray;
+ curLen += stepSize;
+
+ if (curColor.a >= 0.0) {
+ curAlpha = alphaMultiplier * curColor.a;
+ if (curAlpha >= 1.0 || (curColor.a == 1.0 && preserveOpacity == 1))
curAlpha = 1.0;
else
- curAlpha = clamp(curColor.a * totalAlphaMultiplier, 0.0, 1.0);
-
- if (curAlpha > alphaThreshold) {
- curRgb = curColor.rgb * curAlpha * (1.0 - totalAlpha);
- destColor.rgb += curRgb;
- totalAlpha += curAlpha;
+ curAlpha = curAlpha * extraAlphaMultiplier * stepSize;
+ highp float nextOpacity = totalOpacity - curAlpha;
+ // If opacity goes beyond full opacity, we need to adjust current alpha according
+ // to the fraction of the distance the material is visible, so that we don't get
+ // box artifacts around texels.
+ if (nextOpacity < 0.0) {
+ curAlpha *= totalOpacity / curAlpha;
+ nextOpacity = 0.0;
}
- if (i == maxCount || totalAlpha >= 1.0)
- break;
- curPos += step;
+ curRgb = curColor.rgb * curAlpha * (totalOpacity + nextOpacity);
+ totalOpacity = nextOpacity;
+ destColor.rgb += curRgb;
}
+
+ if (curLen >= 1.0 || totalOpacity <= 0.0)
+ break;
}
// Brighten up the final color if there is some transparency left
- if (totalAlpha > alphaThreshold && totalAlpha < 1.0)
- destColor *= 1.0 / totalAlpha;
+ if (totalOpacity >= 0.0 && totalOpacity < 1.0)
+ destColor *= (1.0 - (totalOpacity * 0.5)) / (1.0 - totalOpacity);
- destColor.a = totalAlpha;
+ destColor.a = (1.0 - totalOpacity);
gl_FragColor = clamp(destColor, 0.0, 1.0);
}
diff --git a/src/datavisualization/engine/shaders/texture3dslice.frag b/src/datavisualization/engine/shaders/texture3dslice.frag
index 3d4c9030..00584744 100644
--- a/src/datavisualization/engine/shaders/texture3dslice.frag
+++ b/src/datavisualization/engine/shaders/texture3dslice.frag
@@ -14,8 +14,6 @@ const highp vec3 xPlaneNormal = vec3(1.0, 0, 0);
const highp vec3 yPlaneNormal = vec3(0, 1.0, 0);
const highp vec3 zPlaneNormal = vec3(0, 0, 1.0);
-const highp float alphaThreshold = 0.0001;
-
void main() {
// Find out where ray intersects the slice planes
highp vec3 rayDir = -(cameraPositionRelativeToModel - pos);
@@ -24,7 +22,7 @@ void main() {
// Flip Y and Z so QImage bits work directly for texture and first image is in the front
rayStart.yz = -rayStart.yz;
rayDir.yz = -rayDir.yz;
- highp float tFar = 2.0f;
+ highp float minT = 2.0f;
if (rayDir.x != 0.0 && rayDir.y != 0.0 && rayDir.z != 0.0) {
highp vec3 boxBounds = vec3(1.0, 1.0, 1.0);
highp vec3 invRayDir = 1.0 / rayDir;
@@ -35,24 +33,23 @@ void main() {
if (rayDir.z < 0)
boxBounds.z = -1.0;
highp vec3 t = (boxBounds - rayStart) * invRayDir;
- tFar = min(t.x, t.y);
- tFar = min(tFar, t.z);
+ minT = min(t.x, min(t.y, t.z));
}
highp vec3 xPoint = vec3(volumeSliceIndices.x, 0, 0);
highp vec3 yPoint = vec3(0, volumeSliceIndices.y, 0);
highp vec3 zPoint = vec3(0, 0, volumeSliceIndices.z);
- highp float firstD = tFar + 1.0;
+ highp float firstD = minT + 1.0;
highp float secondD = firstD;
highp float thirdD = firstD;
if (volumeSliceIndices.x >= -1.0) {
highp float dx = dot(xPoint - rayStart, xPlaneNormal) / dot(rayDir, xPlaneNormal);
- if (dx >= 0.0 && dx <= tFar)
+ if (dx >= 0.0 && dx <= minT)
firstD = min(dx, firstD);
}
if (volumeSliceIndices.y >= -1.0) {
highp float dy = dot(yPoint - rayStart, yPlaneNormal) / dot(rayDir, yPlaneNormal);
- if (dy >= 0.0 && dy <= tFar) {
+ if (dy >= 0.0 && dy <= minT) {
if (dy < firstD) {
secondD = firstD;
firstD = dy;
@@ -64,7 +61,7 @@ void main() {
if (volumeSliceIndices.z >= -1.0) {
highp float dz = dot(zPoint - rayStart, zPlaneNormal) / dot(rayDir, zPlaneNormal);
if (dz >= 0.0) {
- if (dz < firstD && dz <= tFar) {
+ if (dz < firstD && dz <= minT) {
thirdD = secondD;
secondD = firstD;
firstD = dz;
@@ -85,14 +82,14 @@ void main() {
// Convert intersection to texture coords
- if (firstD <= tFar) {
+ if (firstD <= minT) {
highp vec3 firstTex = rayStart + rayDir * firstD;
firstTex = 0.5 * (firstTex + 1.0);
curColor = texture3D(textureSampler, firstTex);
if (color8Bit != 0)
curColor = colorIndex[int(curColor.r * 255.0)];
- if (curColor.a > alphaThreshold) {
+ if (curColor.a > 0.0) {
curAlpha = curColor.a;
if (curColor.a == 1.0 && preserveOpacity != 0)
curAlpha = 1.0;
@@ -101,13 +98,13 @@ void main() {
destColor.rgb = curColor.rgb * curAlpha;
totalAlpha = curAlpha;
}
- if (secondD <= tFar && totalAlpha < 1.0) {
+ if (secondD <= minT && totalAlpha < 1.0) {
highp vec3 secondTex = rayStart + rayDir * secondD;
secondTex = 0.5 * (secondTex + 1.0);
curColor = texture3D(textureSampler, secondTex);
if (color8Bit != 0)
curColor = colorIndex[int(curColor.r * 255.0)];
- if (curColor.a > alphaThreshold) {
+ if (curColor.a > 0.0) {
if (curColor.a == 1.0 && preserveOpacity != 0)
curAlpha = 1.0;
else
@@ -116,11 +113,11 @@ void main() {
destColor.rgb += curRgb;
totalAlpha += curAlpha;
}
- if (thirdD <= tFar && totalAlpha < 1.0) {
+ if (thirdD <= minT && totalAlpha < 1.0) {
highp vec3 thirdTex = rayStart + rayDir * thirdD;
thirdTex = 0.5 * (thirdTex + 1.0);
curColor = texture3D(textureSampler, thirdTex);
- if (curColor.a > alphaThreshold) {
+ if (curColor.a > 0.0) {
if (color8Bit != 0)
curColor = colorIndex[int(curColor.r * 255.0)];
if (curColor.a == 1.0 && preserveOpacity != 0)
@@ -136,7 +133,7 @@ void main() {
}
// Brighten up the final color if there is some transparency left
- if (totalAlpha > alphaThreshold && totalAlpha < 1.0)
+ if (totalAlpha > 0.0 && totalAlpha < 1.0)
destColor *= 1.0 / totalAlpha;
destColor.a = totalAlpha;