透明性についての注釈

Duncan Murdoch

24/10/2020

はじめに

透明な面を描画するとき、rgl は透明度をよりよく表現するために、オブジェクトを後ろから前に並べ替えようとします。 しかし、各ピクセルを個別にソートしないため、一部のピクセルは間違った順序で描画されてしまいます。 このノートでは、このエラーの結果について説明し、解決策を提案します。

正しい描画

ここでは、標準的な glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) のブレンディングが使われていると仮定します。 つまり、透明度 \(\alpha\) で描画するとき、新しい色は、先に描画した色(重みは\(1-\alpha\))と \(\alpha\) の割合で混合されます。

このノートでは、2つの透明な物体が同じ場所に描かれたときのことを考えます。 ここでは、遠い方の透明度を \(\alpha_1\) とし、近い方の透明度を \(\alpha_2\) とします。 正しい順番で描かれた場合、3つの色(背景、遠くの物体、近くの物体)は、それぞれ \(C = [(1-\alpha_1)(1-\alpha_2), \alpha_1(1-\alpha_2), \alpha_2]\) の割合で混ざってしまいます。

誤ったソート

オブジェクトが間違った順番で描画された場合、各色の実際の割合は、マスキングが行われない場合、\(N = [(1-\alpha_1)(1-\alpha_2), \alpha_1, \alpha_2(1-\alpha_1)]\)となる。

現在、rgl のデフォルトは、glDepthMask(GL_TRUE) を用いたデプスマスキングです。 これは、オブジェクトが描画されるときに深度が保存され、近いオブジェクトの後にさらに遠いオブジェクトを描画しようとすると(つまり、ここのように)、遠いオブジェクトはカリングされ、比率は \(M = [(1-\alpha_2), 0, \alpha_2]\) となります。

Mask or Not?

問題は、glDepthMask(GL_TRUE)glDepthMask(GL_FALSE) のどちらが良いかということです。 これを測る一つの方法は、\(C\)と正しくない割合の間の距離を測ることです。 (これは、色にも依存する知覚的な距離とは一致しそうにありませんが、何かが必要です。 以下に定性的なコメントを記載します)

つまり、次のようになります。

\[|C-N|^2 = 2\alpha_1^2\alpha_2^2\]

\[|C-M|^2 = 2\alpha_1^2(1-\alpha_2)^2\]

このように、\(\alpha_2 > 1/2\)のときは、\(N\)で誤差が大きくなり、\(\alpha_2 < 1/2\)のときは、\(M\)で誤差が大きくなります。 の値は好みに影響しませんが、\(\pha_1\)の値が小さいほど誤差は小さくなります。

背景の色と2つのオブジェクトの色によっては、この推奨値を変更することができます。 例えば、2つのオブジェクトが同じ色(または非常に近い色)の場合、2番目と3番目の比率をどのように分割するかはあまり重要ではなく、背景の比率が正確に得られる\(N\)が最適となります。

おすすめ

一般的に rgl では、どのオブジェクトがより近く、どのオブジェクトがより遠くにあるかはわかりません。 判断することはできません。 おすすめは、小さなレベルの alpha をすべて使用してマスキングを無効にするか、大きな値の alpha をすべて使用してマスキングを維持することです。

分類不可能なシーンの典型的な例としては、3つの三角形が循環的に配置され、それぞれの三角形が1つ後ろにあり、他の1つ前にあるというものがあります(https://paroj.github.io/gltut/Positioning/Tut05%20Overlap%20and%20Depth%20Buffering.html に基づいています)。

theta <- 2*pi*c(0:2, 4:6, 8:10)/12
x <- cos(theta)
y <- sin(theta)
z <- rep(c(0,0,1), 3)
xyz <- cbind(x, y, z)
xyz <- xyz[c(1,2,6, 4,5,9, 7,8,3),]
open3d()
## null 
##    2
par3d(userMatrix = M)
triangles3d(xyz, col = rep(c("red", "green", "blue"), each = 3))

上の計算の効果を見るために、次の4つのディスプレイを考えてみましょう。

open3d()
## null 
##    4
par3d(userMatrix = M)
layout3d(matrix(1:9, ncol = 3, byrow=TRUE),
         widths = c(1,2,2), heights = c(1, 3,3), 
         sharedMouse = TRUE)
text3d(0,0,0, " ")
next3d()
text3d(0,0,0, "depth_mask = TRUE")
next3d()
text3d(0,0,0, "depth_mask = FALSE")
next3d()
text3d(0,0,0, "alpha = 0.7")
next3d()
triangles3d(xyz, col = rep(c("red", "green", "blue"), each = 3), alpha = 0.7, depth_mask = TRUE)
next3d()
triangles3d(xyz, col = rep(c("red", "green", "blue"), each = 3), alpha = 0.7, depth_mask = FALSE)
next3d()
text3d(0,0,0, "alpha = 0.3")
next3d()
triangles3d(xyz, col = rep(c("red", "green", "blue"), each = 3), alpha = 0.3, depth_mask = TRUE)
next3d()
triangles3d(xyz, col = rep(c("red", "green", "blue"), each = 3), alpha = 0.3, depth_mask = FALSE)

図を回転させると、レンダリングの不完全さがわかる。 右側では、最後に描かれたものが上にあるように見え、左側では、最初に描かれたものが必要以上に不透明に見えます。

下の図では、3つの三角形の透明度がそれぞれ異なっていますが、それぞれ推奨される設定を使用しています。:

open3d()
## null 
##    6
par3d(userMatrix = M)
triangles3d(xyz[1:3,], col = "red", alpha = 0.3, depth_mask = FALSE)
triangles3d(xyz[4:6,], col = "green", alpha = 0.7, depth_mask = TRUE)
triangles3d(xyz[7:9,], col = "blue", depth_mask = TRUE)

この図では、3つの三角形がすべて同じ色になっていますが、表示に影響を与えるのは照明だけです。:

open3d()
## null 
##    8
par3d(userMatrix = M)
layout3d(matrix(1:9, ncol = 3, byrow=TRUE),
         widths = c(1,2,2), heights = c(1, 3,3), 
         sharedMouse = TRUE)
text3d(0,0,0, " ")
next3d()
text3d(0,0,0, "depth_mask = TRUE")
next3d()
text3d(0,0,0, "depth_mask = FALSE")
next3d()
text3d(0,0,0, "alpha = 0.7")
next3d()
triangles3d(xyz, col = "red", alpha = 0.7, depth_mask = TRUE)
next3d()
triangles3d(xyz, col = "red", alpha = 0.7, depth_mask = FALSE)
next3d()
text3d(0,0,0, "alpha = 0.3")
next3d()
triangles3d(xyz, col = "red", alpha = 0.3, depth_mask = TRUE)
next3d()
triangles3d(xyz, col = "red", alpha = 0.3, depth_mask = FALSE)

ここでは、どちらの場合も depth_mask = FALSE が正しい選択と思われます。