PRT(Precomputed Radiance Transfer【2002】)原理实现

2023-10-30

声明

本文源自对Games202课程,作业2的总结。

参考

  1. 手把手教你写GAMES202作业:GAMES202-作业2: Precomputed Radiance Transfer(球谐函数)
  2. GAMES 202 作业2
  3. Games202课程
  4. 个人Blog 课程总结:Games202(P6、P7)环境光照与PRT全局光照

实现目标

实现材质为Diffuse的,基于球谐函数的PRT预处理。
分别实现无阴影有阴影有多次光线弹射的预处理。

注意:

  • 材质类型为Diffuse,不考虑Glossy的物体

一、架构基础

–需要提供光线追踪算法–

需要实现函数:
光线与物体是否相交:scene->rayIntersect(Ray3f ray)
光线与物体相交信息:scene->rayIntersect(Ray3f ray,Intersection its)

–需要提供球谐函数基函数–

1. 使用硬编码

保存前4阶,共16个球谐基函数。

		double HardcodedSH00(const Eigen::Vector3d& d) {
			// 0.5 * sqrt(1/pi)
			return 0.282095;
		}

		double HardcodedSH1n1(const Eigen::Vector3d& d) {
			// -sqrt(3/(4pi)) * y
			return -0.488603 * d.y();
		}

		double HardcodedSH10(const Eigen::Vector3d& d) {
			// sqrt(3/(4pi)) * z
			return 0.488603 * d.z();
		}

		double HardcodedSH1p1(const Eigen::Vector3d& d) {
			// -sqrt(3/(4pi)) * x
			return -0.488603 * d.x();
		}

		double HardcodedSH2n2(const Eigen::Vector3d& d) {
			// 0.5 * sqrt(15/pi) * x * y
			return 1.092548 * d.x() * d.y();
		}

		double HardcodedSH2n1(const Eigen::Vector3d& d) {
			// -0.5 * sqrt(15/pi) * y * z
			return -1.092548 * d.y() * d.z();
		}

		double HardcodedSH20(const Eigen::Vector3d& d) {
			// 0.25 * sqrt(5/pi) * (-x^2-y^2+2z^2)
			return 0.315392 * (-d.x() * d.x() - d.y() * d.y() + 2.0 * d.z() * d.z());
		}

		double HardcodedSH2p1(const Eigen::Vector3d& d) {
			// -0.5 * sqrt(15/pi) * x * z
			return -1.092548 * d.x() * d.z();
		}

		double HardcodedSH2p2(const Eigen::Vector3d& d) {
			// 0.25 * sqrt(15/pi) * (x^2 - y^2)
			return 0.546274 * (d.x() * d.x() - d.y() * d.y());
		}

		double HardcodedSH3n3(const Eigen::Vector3d& d) {
			// -0.25 * sqrt(35/(2pi)) * y * (3x^2 - y^2)
			return -0.590044 * d.y() * (3.0 * d.x() * d.x() - d.y() * d.y());
		}

		double HardcodedSH3n2(const Eigen::Vector3d& d) {
			// 0.5 * sqrt(105/pi) * x * y * z
			return 2.890611 * d.x() * d.y() * d.z();
		}

		double HardcodedSH3n1(const Eigen::Vector3d& d) {
			// -0.25 * sqrt(21/(2pi)) * y * (4z^2-x^2-y^2)
			return -0.457046 * d.y() * (4.0 * d.z() * d.z() - d.x() * d.x()
				- d.y() * d.y());
		}

		double HardcodedSH30(const Eigen::Vector3d& d) {
			// 0.25 * sqrt(7/pi) * z * (2z^2 - 3x^2 - 3y^2)
			return 0.373176 * d.z() * (2.0 * d.z() * d.z() - 3.0 * d.x() * d.x()
				- 3.0 * d.y() * d.y());
		}

		double HardcodedSH3p1(const Eigen::Vector3d& d) {
			// -0.25 * sqrt(21/(2pi)) * x * (4z^2-x^2-y^2)
			return -0.457046 * d.x() * (4.0 * d.z() * d.z() - d.x() * d.x()
				- d.y() * d.y());
		}

		double HardcodedSH3p2(const Eigen::Vector3d& d) {
			// 0.25 * sqrt(105/pi) * z * (x^2 - y^2)
			return 1.445306 * d.z() * (d.x() * d.x() - d.y() * d.y());
		}

		double HardcodedSH3p3(const Eigen::Vector3d& d) {
			// -0.25 * sqrt(35/(2pi)) * x * (x^2-3y^2)
			return -0.590044 * d.x() * (d.x() * d.x() - 3.0 * d.y() * d.y());
		}

		double HardcodedSH4n4(const Eigen::Vector3d& d) {
			// 0.75 * sqrt(35/pi) * x * y * (x^2-y^2)
			return 2.503343 * d.x() * d.y() * (d.x() * d.x() - d.y() * d.y());
		}

		double HardcodedSH4n3(const Eigen::Vector3d& d) {
			// -0.75 * sqrt(35/(2pi)) * y * z * (3x^2-y^2)
			return -1.770131 * d.y() * d.z() * (3.0 * d.x() * d.x() - d.y() * d.y());
		}

		double HardcodedSH4n2(const Eigen::Vector3d& d) {
			// 0.75 * sqrt(5/pi) * x * y * (7z^2-1)
			return 0.946175 * d.x() * d.y() * (7.0 * d.z() * d.z() - 1.0);
		}

		double HardcodedSH4n1(const Eigen::Vector3d& d) {
			// -0.75 * sqrt(5/(2pi)) * y * z * (7z^2-3)
			return -0.669047 * d.y() * d.z() * (7.0 * d.z() * d.z() - 3.0);
		}

		double HardcodedSH40(const Eigen::Vector3d& d) {
			// 3/16 * sqrt(1/pi) * (35z^4-30z^2+3)
			double z2 = d.z() * d.z();
			return 0.105786 * (35.0 * z2 * z2 - 30.0 * z2 + 3.0);
		}

		double HardcodedSH4p1(const Eigen::Vector3d& d) {
			// -0.75 * sqrt(5/(2pi)) * x * z * (7z^2-3)
			return -0.669047 * d.x() * d.z() * (7.0 * d.z() * d.z() - 3.0);
		}

		double HardcodedSH4p2(const Eigen::Vector3d& d) {
			// 3/8 * sqrt(5/pi) * (x^2 - y^2) * (7z^2 - 1)
			return 0.473087 * (d.x() * d.x() - d.y() * d.y())
				* (7.0 * d.z() * d.z() - 1.0);
		}

		double HardcodedSH4p3(const Eigen::Vector3d& d) {
			// -0.75 * sqrt(35/(2pi)) * x * z * (x^2 - 3y^2)
			return -1.770131 * d.x() * d.z() * (d.x() * d.x() - 3.0 * d.y() * d.y());
		}

		double HardcodedSH4p4(const Eigen::Vector3d& d) {
			// 3/16*sqrt(35/pi) * (x^2 * (x^2 - 3y^2) - y^2 * (3x^2 - y^2))
			double x2 = d.x() * d.x();
			double y2 = d.y() * d.y();
			return 0.625836 * (x2 * (x2 - 3.0 * y2) - y2 * (3.0 * x2 - y2));
		}

2. 公式求解基函数

当阶数较大时,无法使用硬编码编写,则需要使用公式直接计算。

  1. 首先根据球谐函数定义,有:
    在这里插入图片描述

  2. 得到 A l m A_l^m Alm (归一化系数)
    在这里插入图片描述

double kml = sqrt(
			(2.0 * l + 1) * Factorial(l - abs(m)) /
			(4.0 * M_PI * Factorial(l + abs(m)))
			);
  1. 求解 P l m ( c o s θ ) P_l^m(cos\theta) Plm(cosθ),根据球谐函数如下性质,求得 c o s θ cos\theta cosθ 在任意基函数的值
    在这里插入图片描述
    注:
    -. 当 P m m ( x ) P_m^m(x) Pmm(x) m = 0 m=0 m=0时,值为1,不是0!(这里公式错了)
    -. 因为基函数可以不需要求解 ( − 1 ) m (-1)^m (1)m (系数的正负可以替代),所以在一些表达式中无正负号。
double EvalLegendrePolynomial(int l, int m, double x) {
	// Compute Pmm(x) = (-1)^m(2m - 1)!!(1 - x^2)^(m/2), where !! is the double factorial.
	double pmm = 1.0;
	// P00 is defined as 1.0, do don't evaluate Pmm unless we know m > 0
	if (m > 0) {
		double sign = (m % 2 == 0 ? 1 : -1);
		pmm = sign * DoubleFactorial(2 * m - 1) * pow(1 - x * x, m / 2.0);
	}

	if (l == m) {
		// Pml is the same as Pmm so there's no lifting to higher bands needed
		return pmm;
	}

	// Compute Pmm+1(x) = x(2m + 1)Pmm(x)
	double pmm1 = x * (2 * m + 1) * pmm;
	if (l == m + 1) {
		// Pml is the same as Pmm+1 so we are done as well
		return pmm1;
	}

	// Use the last two computed bands to lift up to the next band until l is
	// reached, using the recurrence relationship:
	// Pml(x) = (x(2l - 1)Pml-1 - (l + m - 1)Pml-2) / (l - m)
	for (int n = m + 2; n <= l; n++) {
		double pmn = (x * (2 * n - 1) * pmm1 - (n + m - 1) * pmm) / (n - m);
		pmm = pmm1;
		pmm1 = pmn;
	}
	// Pmm1 at the end of the above loop is equal to Pml
	return pmm1;
}

m < 0 m<0 m<0时,根据对称性质
在这里插入图片描述
即:偶数取反,奇数不变。(因为基函数可以不考虑符号正负,故可以认为如下等式成立) P l − m = P l m P_l^{-m} = P_l^m Plm=Plm

  1. 求解 e i m ϕ = c o s ( m ϕ ) + i ⋅ s i n ( m ϕ ) e^{im\phi} = cos(m\phi) + i·sin(m\phi) eimϕ=cos(mϕ)+isin(mϕ)【为什么乘以 2 \sqrt 2 2 ,网上没有找到证明,但是确实乘以 2 \sqrt 2 2 才能得到正确结果!】

    • 当m>0时, e i m ϕ = 2 ∗ c o s ( m ϕ ) e^{im\phi} = \sqrt{2} * cos(m\phi) eimϕ=2 cos(mϕ)
    • 当m<0时, e i m ϕ = 2 ∗ s i n ( − m ϕ ) e^{im\phi} = \sqrt{2} * sin(-m\phi) eimϕ=2 sin(mϕ)
    • 当m=0时, e i m ϕ = 1 e^{im\phi} = 1 eimϕ=1

综上得出最终代码:

if (m > 0) {
	return kml *  sqrt(2.0) * cos(m * phi) *
		EvalLegendrePolynomial(l, m, cos(theta));
}
else if (m < 0) {
	return kml * sqrt(2.0) * sin(-m * phi) *
		EvalLegendrePolynomial(l, -m, cos(theta));
}
else {
	return kml * EvalLegendrePolynomial(l, 0, cos(theta));
}

二、预计算核心算法实现

1. 预计算环境光

S H 系 数 ( l , m ) = ∑ i s a m p l e N u m L e ( i ) ∗ S H 基函 数 ( l , m ) ( D i r ( i ) ) ∗ d A SH系数_{(l,m)} = \sum_i^{sampleNum} Le(i) * SH基函数_{(l,m)}(Dir(i)) * dA SH(l,m)=isampleNumLe(i)SH基函(l,m)(Dir(i))dA
其中:

  • ∑ i s a m p l e N u m = ∑ 天空盒 k 6 ∑ y h e i g h t ∑ x w e i g h t \sum_i^{sampleNum} = \sum_{天空盒k}^6 \sum_y^{height} \sum_x^{weight} isampleNum=天空盒k6yheightxweight
  • i i i 为采样像素点
  • D i r ( i ) Dir(i) Dir(i)为该像素的方向向量
  • d A dA dA 为像素立体角

其中 计算Cubemap的一个像素对应的立体角的大小原理可参照
Solid Angle of A Cubemap Texel - 计算Cubemap的一个像素对应的立体角的大小

// 计算球谐系数
float sumWeight = 0;
for (int i = 0; i < 6; i++)
{
    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            // TODO: 此处你需要计算每个像素下cubemap某个面的球谐系数
            // 方向 
            Eigen::Vector3f dir = cubemapDirs[i * width * height + y * width + x];
            // 入射光
            int index = (y * width + x) * channel;
            Eigen::Array3f Le(images[i][index + 0], images[i][index + 1],
                              images[i][index + 2]);
            // 立体角
            float dA = CalcArea(x, y, width, height);
            
            // 计算球谐系数
            for (int l = 0; l <= SHOrder; l++) {
                for (int m = -l; m <= l; m++) {
                    // Eigen库不能在Vector3d,Vector3f之间相互赋值
                    double basic_fun_value = sh::EvalSHSlow(l, m, Eigen::Vector3d(dir.x(), dir.y(), dir.z()).normalized());
                    SHCoeffiecents[sh::GetIndex(l, m)] += Le * basic_fun_value * dA;
                }
            }
        }
    }
}
return SHCoeffiecents;

2. 预计算传输项球谐函数

∫ Ω V ( i ) B R D F ( i , o ) m a x ( n ⋅ w i , 0 ) d i \int_{\Omega}\quad V(i)\quad BRDF(i,o) \quad max(n\cdot w_i,0)\quad di ΩV(i)BRDF(i,o)max(nwi,0)di
注意:若材质为Diffuse,则 B R D F ( i , 0 ) = 1 / π BRDF(i,0)=1/\pi BRDF(i,0)=1/π;

将函数展开到球谐函数上,函数有:

  • 无阴影函数
  • 有阴影函数
  • 有阴影且多次反射函数

这些函数都是离散的,所以需要采样来得到。

无阴影函数

输入:顶点法线 n n n、采样方向 w i w_i wi
输出: 1 π m a x ( n ⋅ w i , 0 ) \frac{1}{\pi} max(n\cdot w_i,0) π1max(nwi0)

有阴影函数

输入:顶点位置 v v v,顶点法线 n n n、采样方向 w i w_i wi
处理: V i s i b i l i t y = 在 v 点向 w i 方向发射射线,检测是否碰到物体 Visibility =在v点向w_i方向发射射线,检测是否碰到物体 Visibility=v点向wi方向发射射线,检测是否碰到物体
输出: 1 π m a x ( n ⋅ w i , 0 ) ∗ V i s i b i l i t y \frac{1}{\pi} max(n\cdot w_i,0) * Visibility π1max(nwi0)Visibility

有阴影且多次反射函数

需要先进行有阴影函数处理,得到每个顶点的SH系数。
对于每个顶点,递归调用函数computeInterreflectionSH(&m_TransportSHCoeffs, v, n, scene, 1)

Func(求解顶点的球谐系数T,顶点位置v,顶点法线n,场景指针,当前弹射次数){
	if (当前弹射次数>最大弹射次数) return 球谐系数为0;
	对每一个顶点在球面采样
	for(theta = 0->pi, phi = 0->2*pi){
		1. 在采样方向与物体求交,得到求交信息(三角形顶点编号,交点位置,重心坐标);
		2. 插值计算得到相交点球谐函数系数interpolateSH。
		3. 递归调用Func(相交点信息),得到弹射返回的传输项球谐系数nextBouncesCoeffs。
		4. 将interpolateSH(当前采样方向直接传输项) + nextBouncesCoeffs(当前采样方向多次弹射传输项)
	}
	得到采样方向的总传输项归一化(*coeffs)[i] /= sample_side * sample_side;
	并将这数据return;
}

源码:

    /// <summary>
    /// 递归计算相互反射
    /// </summary>
    /// <param name="directTSHCoeffs">当前顶点传输项球谐系数</param>
    /// <param name="pos">顶点位置</param>
    /// <param name="normal">顶点法线</param>
    /// <param name="scene">场景</param>
    /// <param name="bounces">弹射次数</param>
    /// <returns></returns>
    std::unique_ptr<std::vector<double>> computeInterreflectionSH(Eigen::MatrixXf* directTSHCoeffs, const Point3f& pos, const Normal3f& normal, const Scene* scene, int bounces)
    {
        std::unique_ptr<std::vector<double>> coeffs(new std::vector<double>());
        coeffs->assign(SHCoeffLength, 0.0);

        if (bounces > m_Bounce)
            return coeffs;

        // 弹射次数
        const int sample_side = static_cast<int>(floor(sqrt(m_SampleCount)));
        std::random_device rd;
        std::mt19937 gen(rd());
        std::uniform_real_distribution<> rng(0.0, 1.0);
        for (int t = 0; t < sample_side; t++) {
            for (int p = 0; p < sample_side; p++) {
                double alpha = (t + rng(gen)) / sample_side;
                double beta = (p + rng(gen)) / sample_side;
                double phi = 2.0 * M_PI * beta;
                double theta = acos(2.0 * alpha - 1.0);

                Eigen::Array3d d = sh::ToVector(phi, theta);
                const auto wi = Vector3f(d.x(), d.y(), d.z());
                double H = wi.normalized().dot(normal);
                // 光线与物体求交
                Intersection its;
                if (H > 0.0 && scene->rayIntersect(Ray3f(pos, wi.normalized()), its))
                {
                    MatrixXf normals = its.mesh->getVertexNormals();//顶点法线
                    Point3f idx = its.tri_index; //三角形序号
                    Point3f hitPos = its.p;      //交点位置
                    Vector3f bary = its.bary;    // 重心坐标

                    // 计算得到法线插值
                    Normal3f hitNormal =
                        Normal3f(normals.col(idx.x()).normalized() * bary.x() +
                            normals.col(idx.y()).normalized() * bary.y() +
                            normals.col(idx.z()).normalized() * bary.z())
                        .normalized();

                    // 递归调用
                    auto nextBouncesCoeffs = computeInterreflectionSH(directTSHCoeffs, hitPos, hitNormal, scene, bounces + 1);

                    // 复写SH系数
                    for (int i = 0; i < SHCoeffLength; i++)
                    {
                        //插值得到重心坐标传输项球谐函数
                        auto interpolateSH = (directTSHCoeffs->col(idx.x()).coeffRef(i) * bary.x() +
                            directTSHCoeffs->col(idx.y()).coeffRef(i) * bary.y() +
                            directTSHCoeffs->col(idx.z()).coeffRef(i) * bary.z());

                        // 因为这里假设所有表面都是漫反射,所以每个方向的光照都是相同的。
                        // 因此,间接光照的值: L_间接 = L * T_间接 (L为环境光 * T为传输项)
                        // 因此,该方向的直接光照 = L * T_直接 + L_间接 = L * T_直接 + L * T_间接
                        // (注意:这里 (L_间接) 不用乘以 (1- T_直接) 因为我们做光线追踪时,只有(1- T_直接)范围内才有间接光)
                        // 综上:当前顶点传输项 T = T + T_间接,因此用加号直接相加即可
                        (*coeffs)[i] += (interpolateSH + (*nextBouncesCoeffs)[i]) * H;
                    }
                }
            }
        }

        // 这里不应该简单的除以sample_side * sample_side,
        // 而是应该在采样中乘以立体角,而立体角=4pi/采样数
        // 所以应该乘以4pi/sample_side * sample_side

		// 源码
        //for (unsigned int i = 0; i < coeffs->size(); i++) {
        //    (*coeffs)[i] /= sample_side * sample_side;
        //}
        // 修改代码
        float invSample = 1 / sample_side * sample_side;
        for (unsigned int i = 0; i < coeffs->size(); i++) {
            (*coeffs)[i] = 4.0f * M_PI * invSample;
        }
        return coeffs;
    }

3. 保存计算得到的L和T的球谐系数

保存为txt文档

三、使用预计算数据进行渲染

  • 将预计算好的光照球谐系数作为Uniform参数传入Shader
  • 将预计算好的顶点传输项系数作为顶点数据传入Shader

使用顶点着色方式着色

//prtVertex.glsl
attribute vec3 aVertexPosition;
attribute vec3 aNormalPosition;
attribute mat3 aPrecomputeLT;

uniform mat4 uModelMatrix;
uniform mat4 uViewMatrix;
uniform mat4 uProjectionMatrix;

uniform mat3 uPrecomputeL[3];

varying highp vec3 vColor;

float L_dot_LT(mat3 PrecomputeL, mat3 PrecomputeLT) {
	  vec3 L_0 = PrecomputeL[0];
	  vec3 L_1 = PrecomputeL[1];
	  vec3 L_2 = PrecomputeL[2];
	  vec3 LT_0 = PrecomputeLT[0];
	  vec3 LT_1 = PrecomputeLT[1];
	  vec3 LT_2 = PrecomputeLT[2];
	  return dot(L_0, LT_0) + dot(L_1, LT_1) + dot(L_2, LT_2);
}

void main(void) {
	  for(int i = 0; i < 3; i++)
	  {
	    vColor[i] = L_dot_LT(aPrecomputeLT, uPrecomputeL[i]);
	  }
	  gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aVertexPosition, 1.0);
}

将颜色直接输出(经过色调映射)

//prtFragment.glsl
#ifdef GL_ES
precision mediump float;
#endif
varying highp vec3 vColor;

vec3 toneMapping(vec3 color){
    vec3 result;
      for (int i=0; i<3; ++i) {
        result[i] = pow(color[i], 0.45);
    }

    return result;
}

void main(){
  vec3 color = toneMapping(vColor); 
  gl_FragColor = vec4(color, 1.0);
}

四、基于漫反射材质的PRT总结

可以看出,基于漫反射材质的PRT,本质上就是计算好每个顶点接收四面八方光照的方程,再与光照相乘得到最终结果。
在这里插入图片描述

什么时候会用到快速的场景切换呢?

我们已知,PRT要求预计算的场景不可动,只能切换或旋转球谐光源。所以PRT只能用于静态物体;而在多个球谐光源之间插值或转换,也可以理解为切换球谐光源。

所以可以用在:

  1. 凌晨,早晨,正午,下午,晚上,傍晚几个球谐光源之间转化,来改变场景中动态物体的全局光照。
  2. 如果只对不发生形变的物体,在局部空间下计算传输项,则可以在场景移动,根据周围环境的光照探针计算光照,只是物体不能发生形变,且只能计算自遮挡,无法被环境中其他物体遮挡产生阴影。

与光照烘培的区别

如果不改变环境光照,PRT可被替代为光照烘培,即直接计算每个点在当前环境下的颜色值,并记录在一张纹理上。

因此,基于漫反射的PRT相比于光照烘培,只是多了环境光可旋转,可改变这样一个条件,而付出的代价则是9倍(3阶球谐记录)的存储空间。

那么PRT有什么优势呢?????

五、基于Glossy物体的PRT

每个顶点多记录一维数据(视口俯仰角度),根据摄像机方向和物体法线方向的夹角,得到该夹角下的球谐系数,再计算得到该夹角下的颜色值。

如何得到Glossy物体系数矩阵???
之后再补充!!

这相比于烘培就有很大的优势了,可以实时得到光泽反射的效果(区别于镜面反射,即可以反射一定的全局光照,但只是一个很模糊的结果),这是光照烘培做不到的。

但开销同样巨大,一般情况下,光泽反射需要的阶数更高,通常需要16 * 16(4阶)或25 * 25(5阶)倍的显存开销。

六、游戏界环境是否使用PRT来做全局光照

现代实时渲染 一般不使用 PRT来做全局,
一是因为对显存开销巨大
二是因为现代GPU性能相比于预处理年代【2002】已经好了很多倍,不需要如此大量的预处理,很多计算可以放如GPU中实时计算。
三是因为PRT应用了球谐函数做拟合,本身就是一种很大的近似,而现代工业中,如果需要做全局光照的,一般是3A游戏,而PRT的效果显然达不到。

那我们就没必要学习吗??

不,PRT开创了通过预计算得到实时全局光照的思想。
PRT 技术或许很少用在工业中,但这种思想则被用在各种技术中!预计算得到实时全局光照的思想,之后可能会更多的应用到VR,数字孪生,开放世界中。

另外两类全局光照的开创性代表是:

  • RSM为代表的实时计算全局光照(VXGI,DDGI等)
  • SSAO为代表的屏幕空间全局光照(HBAO,SSR等)

这两类则更为我们所熟知!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

PRT(Precomputed Radiance Transfer【2002】)原理实现 的相关文章

  • 相机 (2)旋转

    1 1 LookAt矩阵 用这3个轴外加一个平移向量来创建一个矩阵 并且你可以用这个矩阵乘以任何向量来将其变换到那个坐标空间 R是右向量 U是上向量 D是方向向量 P是摄像机位置向量 glm LookAt函数需要一个位置 目标和上向量以得到
  • Vulkan-实践第一弹

    上一篇文章中 我们浅析了Vulkan对传统图形API的优势 主要就是在其性能和精细化操控GPU上 具体可参考Vulkan 性能及精细化 今天我们就来用个简单的例子 亲身感受下Vulkan的开发 魅力 include
  • Vulkan基础

    目录 一 Vulkan开发理论基础知识 接口设计理念 Host Device 基础设施 元数据和设备 基础设施 交换链 编辑交换链 SwapChain 编辑 渲染管线 Pipeline RenderPass CommandBuffer 二
  • 【Games101 作业6 + 附加题】渲染兔子 BVH SAH 代码

    基础题部分 根据教程PDF 首先需要引用如下函数 在作业5的基础上稍作修改 renderer in Renderer cpp 解说见注释 The main render function This where we iterate over
  • IBL-镜面反射(预滤波篇)

    文章目录 1低差异序列 2重要性采样 3 GGX重要性采样 3 1 将uv坐标转化为半球向量坐标 3 2 将半球向量坐标转化为笛卡尔坐标 3 3 将切线坐标转化为世界坐标 3 4 完整代码 4 预滤波器卷积着色器 4 1 近似 4 2 获取
  • 【论文精读】NeRF详解

    最近阅读了开启三维重建新纪元的经典文章 NeRF Representing Scenes as Neural Radiance Fields for View Synthesis 接下来会 更新NeRF系列的论文精读 代码详解 力求做到全网
  • [游戏开发][Shader]GLSLSandBox转Unity-CG语言

    官网 GLSL Sandbox Galleryhttps glslsandbox com 屏幕坐标计算 fragCoord与 ScreenParams mat2矩阵转换 vec2向量 在GLSL里mat2 a b c d 函数返回vec2
  • 图形基础-叉乘

    using System Collections using System Collections Generic using UnityEngine public class Test MonoBehaviour public Vecto
  • 用云渲染好还是自己搭建传统渲染农场好?

    今天云渲染小编就和大家说说云渲染以及它和传统渲染农场的区别 以及用云渲染好还是自己搭建传统渲染农场好 一 什么是云渲染 云渲染什么意思 首先云渲染是一种依托于云计算的云端服务 用户将本地文件提交到云端服务器 云端计算机集群完成渲染 再将渲染
  • 2021年 IEEE VIS 科学可视化与体渲染论文整理与分析

    因为最近工作的关系 需要研究一下IEEE VIS中2017年以后的与我之前主要方向 体渲染 医学可视化 有关的论文 我把这些年全部的论文进行了筛选和梳理 总共筛选出57篇论文 打算写一个文章来记录这些内容 这个栏目是2021年的九篇论文的介
  • 初识OpenGL (-)纹理过滤(Texture Filtering)

    1 OpenGL需要知道怎样将纹理像素 Texture Pixel 也叫Texel 映射到纹理坐标 纹理坐标 不依赖于分辨率 Resolution 它可以是任意浮点值 给模型顶点设置的那个数组 OpenGL以这个顶点的纹理坐标数据去查找纹理
  • 关于OpenGL纹理尺寸的坑 - 图像行偏移,出现异色条纹

    学习OpenGL时想简单创建一个纹理 但马上就出现错误 错误效果如下 原图如下 由于之前没有碰过这种问题 费了好大一番功夫才找到问题所在 原始图片尺寸为210 220 OpenGL版本与教程一致 为3 3 把像素值打印出来观察之后发现 传入
  • 【机器人仿真Webots教程】-Webots安装

    Webots安装 文章目录 Webots安装 1 Webots简介 2 Webots安装 2 1 系统要求 2 2 验证显卡驱动 2 3 安装 3 Webots仿真 3 1 world文件 3 2 Controller文件 3 3 Supe
  • GAMES101: 现代计算机图形学入门(2)几何、光线追踪

    GAMES101 现代计算机图形学入门 链接 GAMES101 1 几何 1 1 几何的表示 隐式几何 通过一个函数表达式来表示的几何体 即 f x y z 0 优点 很容易判断一个点在不在几何体上 缺点 很难通过表达式看出几何体的形状 显
  • DirectX12 3D游戏开发实践(龙书)第四章 Direct3D的初始化

    目录 Direct3D的初始化 预备知识 Direct3D概述 组件对象模型 纹理格式 Textures Formats 交换链和页面翻转 深度缓冲 资源与描述符 多重采样技术的原理 利用Direct3D进行多重采样 功能级别 Direct
  • Shader学习笔记:BRDF简单概述

    这篇文章写于一年多以前的一次课程作业 这次作为一个 存货 给放出来 仅仅只是针对代码和一些要点进行简单叙述 如果想听完整的版本 请搜索毛星云大神的博客或者书籍 关于基本的物理渲染公式 网络上的博客和典籍已经多如牛毛了 这里只是自己在之前整理
  • 图形学基础1

    坐标系相关 uv可能会影响局部坐标系 如果light图和brdf图做卷积的时候 局部坐标系保持一致很重要 如下图 tangent是从外部模型文件进行加载的 切线空间采样并转世界坐标系 spherical to cartesian in ta
  • The Cherno——OpenGL

    The Cherno OpenGL 1 欢迎来到OpenGL OpenGL是一种跨平台的图形接口 API 就是一大堆我们能够调用的函数去做一些与图像相关的事情 特殊的是 OpenGL允许我们访问GPU Graphics Processing
  • 【Unity灯光与渲染技术】Global Illumination全局光照

    本系列主要参考Unity灯光与渲染技术教程Unity Lighting And Rendering 同时会加上一点个人实践过程和理解 分割线 这篇文章主要讲全局光照 在看教程的时候就有一个点不是很理解 就是作者开启物体的static这个选项
  • 蒙特卡洛积分、重要性采样、低差异序列

    渲染公式 渲染的目标在于计算周围环境的光线有多少从表面像素点反射到相机视口中 要计算总的反射光 每个入射方向的贡献 必须将他们在半球上相加 为入射光线 与法线 的夹角 为方便计算可以使用法线向量和入射向量 单位化 的乘积表示 对于基于图像的

随机推荐

  • 关于Qt中常用的延时方法--自我总结

    一般情况下 延时大概分为两类 一个是非阻塞延时 一个是阻塞延时 但从名称上应该都可以看出来具体的含义 下面针对这两类延时方法 做一个具体的说明和代码实现 一 关于Qt实现非阻塞延时的方法 void QSleepTimeSet Delay M
  • 机器视觉——OpenCV案例分析基础(七)(边缘检测和图像轮廓查找)

    边缘检测和图像轮廓查找 一 理论分析 二 代码分析 2 1 边缘检测 2 1 1 Sobel算子 2 1 2 Scharr算子 2 1 3 Laplacian算子 2 1 4 Canny算子 去噪 梯度 非极大值抑制 滞后阈值 2 2 特征
  • 【MATLAB项目实战】基于MATLAB的发票识别(含GUI界面)

    Matlab 发票识别 思路 灰度化 二值化 形态学操作 膨胀 形态学滤波开运算 找到各个模块分割 对各个模块进行字符分割 模版匹配最终得到结果 发票 function varargout GUI varargin GUI MATLAB c
  • vue开发项目(PC端和移动端共用一套代码)(一)

    编写两套代码 通过路由加载不同端的文件 1 创建vue项目 2 基本配置 2 1 html设置 创建两端的vue文件 在App vue中 添加 2 2 路由设置 在router文件夹下 创建m pc两个文件夹 路径如下 router m i
  • 从 Windows 过度到 Mac 必备快捷键对照表

    Mac 键盘符号 图标 介绍 Command Shift Caps Lock Option Alt Control Enter Delete Fn Delete 上箭头 下箭头 左箭头 右箭头 Fn Page Up Fn Page Down
  • vue 从入门到入土---复习 webpack

    目录 前端工程化 wbepack的基本使用 webpack中的插件 webpack中的loader 打包发布 Socure Map 总结 前端工程化 1 了解前端工程化概念 前端工程化指的是 在企业级的前端项目开发中 把前端所需要的开发工具
  • 服务器运行mysql的时候出现:Error response from daemon: Container xxx is not running

    服务器运行mysql的时候出现 Error response from daemon Container xxx is not running 我是安装完mysql以后 通过这条命令进入mysql容器的时候出现报错 docker exec
  • Maven之自定义archetype生成项目骨架

    http blog csdn net sxdtzhaoxinguo article details 46895013 摘要 使用过Maven的人都知道maven中有许多功能都是通过插件来提供的 今天我们来说一下其中的一个插件 maven a
  • 安卓app开发零基础入门培训!腾讯Android开发面试记录,技术详细介绍

    开头 都说程序员是在吃青春饭 这一点的确有一点对的成分 以前我不这么认为 但随着年龄的增长 事实告诉我的确是这样的 过了30以后 就会发现身体各方面指标下降 体力和身心上都多少有点跟不上了 这个年龄往往是很尴尬的 与年轻的程序员相比 产出没
  • HBuilderX使用Vant组件库

    HBuilderX使用Vant组件库 原创地址 HBuilderX使用Vant组件库 HBuilderX是一款由国人开发的开发工具 其官网称其为轻如编辑器 强如IDE的合体版本 但是官方的社区中关于Vant组件的安装大多都是针对微信小程序开
  • win电脑go的安装

    官网打开 https go dev dl 默认安装到了c盘 然后配置一下环境变量就可以了 go run 1 go
  • nrm安装后报错

    原文链接 https juejin cn post 7212960463730819127 Error ERR REQUIRE ESM require of ES Module D npm node modules nrm node mod
  • DFS 相关例题

    会顶科技 两道 第一行为一个整数N 用来表示球的个数 第二行为一个整数M 用来表示桶的个数 从第三行到第N 2行 每行有M个整数 这些整数要么为0 要么为1 输出描述 输出为一个整数 输出1代表可以放入所有的球 输出0代表无法放入所有的球
  • android开发:Android Studio的Signature Versions选择

    参考 https blog csdn net jiangjingxuan article details 66970552
  • Anaconda安装jieba库和wordcloud库安装实现词云

    一 jieba库安装 1 从官网下载jieba压缩包 https pypi org project jieba files 2 将压缩包解压到anaconda的pkgs目录 3 打开anaconda prompt 切换目录至比如 C ana
  • Linux环境下编程遇到“fatal error:stdio.h:没有那个文件或目录”错误解决办法

    我是荔园微风 作为一名在IT界整整25年的老兵 今天总结一下linux环境下如何解决一个常见的问题 也就是 fatal error stdio h 没有那个文件或目录 错误 不少初学者在linux环境下用gcc编译C语言时 经常会遇到这个问
  • Linux wc命令

    Linux wc命令 作用 统计字节数 字符数 行数 最长的行的长度 单词数 格式 wc OPTION FILE wc OPTION files0 from F OPTION c 或 bytes 计算字节数 m 或 chars 计算字符数
  • SPA项目开发之首页导航+左侧栏菜单

    文章目录 后台主界面搭建 左侧树收缩功能 vue总线的概念 后台主界面搭建 在搭建主界面之前 来给大家介绍一个MOCK js 是一个模拟数据的生成器 用来帮助前端调试开发 进行前后端的原型分离以及用来提高自动化测试效率 众所周知Mock j
  • hex码和ascii码的转换

    hex码和ascii码的转换 2017年01月09日 17 48 25 changeyourmind 阅读数 4784 版权声明 本文为博主原创文章 未经博主允许不得转载 https blog csdn net changyourmind
  • PRT(Precomputed Radiance Transfer【2002】)原理实现

    声明 本文源自对Games202课程 作业2的总结 参考 手把手教你写GAMES202作业 GAMES202 作业2 Precomputed Radiance Transfer 球谐函数 GAMES 202 作业2 Games202课程 个