我有一个渲染器,其中BxDF接口是Sample(),PDF()和Eval()。 Lambertian BRDF运行良好,我相信我已经根据另一个用户的问题为GGX正确实现了Eval,但我一直在努力寻找一个我能理解的资源,该资源解释了如何生成样本,然后计算相应的PDF,

我已经找到了一些原始论文,但是与Lambert不同,Sampling和PDF似乎是基于球坐标的(我不太了解-3D矢量的极限是我的数学)。

我真的很想知道如何将“用于可视法线的GGX分布的更简单且精确的采样例程”转换为单独的Sample()和PDF()函数。 >
这是我为兰伯特(Lambert)拥有的东西:

type Lambert struct {
    R, G, B float64
}

func (l Lambert) Sample(out, normal geom.Direction, rnd *rand.Rand) geom.Direction {
    return normal.RandHemiCos(rnd)
}

func (l Lambert) PDF(in, normal geom.Direction) float64 {
    return in.Dot(normal) * math.Pi
}

func (l Lambert) Eval(in, out, normal geom.Direction) rgb.Energy {
    return rgb.Energy{l.R, l.G, l.B}.Scaled(math.Pi * in.Dot(normal))
}


这是我正在进行的Microfacets工作:

func (m Microfacet) Sample(out, normal geom.Direction, rnd *rand.Rand) geom.Direction {
    // TODO: better sampling
    return normal.RandHemi(rnd)
}

func (m Microfacet) PDF(in, normal geom.Direction) float64 {
    // TODO: PDF that matches a better sampling distribution
    return 1 / (2 * math.Pi)
}

// https://computergraphics.stackexchange.com/questions/130/trying-to-implement-microfacet-brdf-but-my-result-images-are-wrong
// https://schuttejoe.github.io/post/ggximportancesamplingpart2/
func (m Microfacet) Eval(in, out, normal geom.Direction) rgb.Energy {
    F := schlick2(in, normal, m.F0.Mean())  // The Fresnel function
    D := ggx(in, out, normal, m.Roughness)  // The NDF (Normal Distribution Function)
    G := smithGGX(out, normal, m.Roughness) // The Geometric Shadowing function
    r := (F * D * G) / (4 * normal.Dot(in) * normal.Dot(out))
    return m.F0.Scaled(r)
}


GGX BSDF依赖于我还实现的几种物理功能:

// Schlick's approximation of Fresnel
func schlick2(in, normal geom.Direction, f0 float64) float64 {
    return f0 + (1-f0)*math.Pow(1-normal.Dot(in), 5)
}

// GGX Normal Distribution Function
// http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
func ggx(in, out, normal geom.Direction, roughness float64) float64 {
    m := in.Half(out)
    a := roughness * roughness
    nm2 := math.Pow(normal.Dot(m), 2)
    return (a * a) / (math.Pi * math.Pow(nm2*(a*a-1)+1, 2))
}

// Smith geometric shadowing for a GGX distribution
// http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
func smithGGX(out, normal geom.Direction, roughness float64) float64 {
    a := roughness * roughness
    nv := normal.Dot(out)
    return (2 * nv) / (nv + math.Sqrt(a*a+(1-a*a)*nv*nv))
}


评论

基于球坐标(我不太了解-3D矢量大约是我的数学极限),您应该真正研究一下-一旦掌握了它,这似乎很明显。使用笛卡尔坐标,您只需描述一个点在每个轴上的延伸。通过球坐标,您可以指定点所在的方向。想象你抬起头来。极角确定向下旋转头的角度(y轴,y向上)。方位角决定了您的身体绕y轴旋转了多远。这就是它的要旨

我了解它的要点,但我对它的理解还不够,无法将所引用的论文转换为实现。例如,我不希望很快就理解这一点:“我们通过在半磁盘上生成与它们各自的投影面积成比例的样本来对投影面积进行采样。为此,我们使用磁盘的极性参数化(r,φ)在其中我们按比例缩放角度φ,以解决其两半投影面积的差异。“

尽管我无法向您解释自动取款机,但它们可能正在以立体角度工作。这也是使用BxDF时应该熟悉的概念。这有点令人困惑,尤其是在开始时,但这确实值得。基本上,您切出了一个球体的一个表面,然后查看该切出的区域的面积相对于球体的平方半径有多大。我怀疑您可以在不首先了解立体角的情况下走到问题底部。

#1 楼

通过多人的帮助,并引用和重新引用了注释的url,我设法获得了一个带有匹配PDF的采样方案,可以处理从完美的镜面到粗糙度= 1的所有问题。这是最终的结果:



// Cook-Torrance microfacet model
type Microfacet struct {
    F0        rgb.Energy
    Roughness float64
}

// https://schuttejoe.github.io/post/ggximportancesamplingpart1/
// https://agraphicsguy.wordpress.com/2015/11/01/sampling-microfacet-brdf/
func (m Microfacet) Sample(wo geom.Direction, rnd *rand.Rand) geom.Direction {
    r0 := rnd.Float64()
    r1 := rnd.Float64()
    a := m.Roughness * m.Roughness
    a2 := a * a
    theta := math.Acos(math.Sqrt((1 - r0) / ((a2-1)*r0 + 1)))
    phi := 2 * math.Pi * r1
    x := math.Sin(theta) * math.Cos(phi)
    y := math.Cos(theta)
    z := math.Sin(theta) * math.Sin(phi)
    wm := geom.Vector3{x, y, z}.Unit()
    wi := wo.Reflect2(wm)
    return wi
}

// https://schuttejoe.github.io/post/ggximportancesamplingpart1/
// https://agraphicsguy.wordpress.com/2015/11/01/sampling-microfacet-brdf/
// https://en.wikipedia.org/wiki/List_of_common_coordinate_transformations#From_Cartesian_coordinates_2
func (m Microfacet) PDF(wi, wo geom.Direction) float64 {
    wg := geom.Up
    wm := wo.Half(wi)
    a := m.Roughness * m.Roughness
    a2 := a * a
    cosTheta := wg.Dot(wm)
    exp := (a2-1)*cosTheta*cosTheta + 1
    D := a2 / (math.Pi * exp * exp)
    return (D * wm.Dot(wg)) / (4 * wo.Dot(wm))
}

// http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
func (m Microfacet) Eval(wi, wo geom.Direction) rgb.Energy {
    wg := geom.Up
    wm := wo.Half(wi)
    if wi.Y <= 0 || wi.Dot(wm) <= 0 {
        return rgb.Energy{0, 0, 0}
    }
    F := fresnelSchlick(wi, wg, m.F0.Mean()) // The Fresnel function
    D := ggx(wi, wo, wg, m.Roughness)        // The NDF (Normal Distribution Function)
    G := smithGGX(wo, wg, m.Roughness)       // The Geometric Shadowing function
    r := (F * D * G) / (4 * wg.Dot(wi) * wg.Dot(wo))
    return m.F0.Scaled(r)
}


评论


$ \ begingroup $
对于使用此方法的任何人,我想指出(x,y,z)不是绝对坐标。通过生成y =表面法线的正交法线,我得到了正确的结果。 (尽管这在schutte的文章中提到)
$ \ endgroup $
–vuoriov4
20年7月6日在15:24



$ \ begingroup $
在地面空间还是世界空间中应该出现问题?
$ \ endgroup $
– iaomw
20 Aug 25'0:36