- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
之前完成了利用OpenGL实现GPU体渲染的实验,现在把完成的工作做一个总结.
本实验demo的完成主要参考了 《OpenGL – Build high performance graphics》 这本书的体渲染部分和其中的代码,也参考了 体绘制光线投射算法 这篇博客。关于体渲染的ray-casting光线投射算法原理这里不再介绍,本文主要讲述实现过程.
以下是具体实现过程:
体数据可视化如图所示,产生体数据的代码如下:
int Dim[3] = { 200,200,200 };//体数据维度大小
int* Data = (int*)malloc(sizeof(int) * Dim[0] * Dim[1] * Dim[2]);
GLubyte CData[200][200][200][4];//存储颜色和不透明度
glm::vec4 smallCubeC = glm::vec4(1.0, 1.0, 0.0, 1.0);//小立方体颜色
glm::vec4 middleSphereC = glm::vec4(1.0, 0.0, 0.0, 1.0);//中间球体颜色
glm::vec4 largeCubeC = glm::vec4(1.0, 1.0, 1.0, 1.0);//大立方体颜色
float smallCubeD = 0.05;//小立方体不透明度
float middleSphereD = 0.015;//中间球体不透明度
float largeCubeD = 0.018;//大立方体不透明度
void GenCube(int x, int y, int z, int side, int density, int* Data, int* Dim)
{
int max_x = x + side, max_y = y + side, max_z = z + side;
int Dimxy = Dim[0] * Dim[1];
for (int k = z; k < max_z; k++)
{
for (int j = y; j < max_y; j++)
{
for (int i = x; i < max_x; i++)
{
Data[k * Dimxy + j * Dim[0] + i] = density;
}
}
}
}
void GenSphere(int x, int y, int z, int radius, int density, int* Data, int* Dim)
{
int radius2 = radius * radius;
int Dimxy = Dim[0] * Dim[1];
for (int k = 0; k < Dim[2]; k++)
{
for (int j = 0; j < Dim[1]; j++)
{
for (int i = 0; i < Dim[0]; i++)
{
if ((i - x) * (i - x) + (j - y) * (j - y) + (k - z) * (k - z) <= radius2)
{
Data[k * Dimxy + j * Dim[0] + i] = density;
}
}
}
}
}
void Classify(GLubyte CData[200][200][200][4], int* Data, int* Dim)//按照所在位置为每个体数据点赋值,颜色和不透明度
{
int* LinePS = Data;
for (int k = 0; k < Dim[2]; k++)
{
for (int j = 0; j < Dim[1]; j++)
{
for (int i = 0; i < Dim[0]; i++)
{
if (LinePS[0] <= 100)
{
//白色
CData[i][j][k][0] = 255.0 * largeCubeC[0];
CData[i][j][k][1] = 255.0 * largeCubeC[1];
CData[i][j][k][2] = 255.0 * largeCubeC[2];
CData[i][j][k][3] = largeCubeD*255.0;
}
else if (LinePS[0] <= 200)
{
//红色
CData[i][j][k][0] = 255.0 * middleSphereC[0];
CData[i][j][k][1] = 255.0 * middleSphereC[1];
CData[i][j][k][2] = 255.0 * middleSphereC[2];
CData[i][j][k][3] = middleSphereD*255.0;
}
else
{
//黄色
CData[i][j][k][0] = 255.0 * smallCubeC[0];
CData[i][j][k][1] = 255.0 * smallCubeC[1];
CData[i][j][k][2] = 255.0 * smallCubeC[2];
CData[i][j][k][3] = smallCubeD*255.0;
}
LinePS++;
}
}
}
//return CDdata[200][200][200][4];
}
void GenerateVolume(int* Data, int* Dim)
{
GenCube(0, 0, 0, 200, 100, Data, Dim);//大正方体
GenSphere(100, 100, 100, 80, 200, Data, Dim);//球体
GenCube(70, 70, 70, 60, 300, Data, Dim);//小正方体
}
手动生成的体数据会更有利于理解光线投射算法体渲染的原理,该体数据本质上就是200x200x200个点,每个点赋予了对应的颜色值和不透明度.
为了在着色器中实现ray-casting光线投射,合成像素值,需要将体数据存入三维纹理中,然后传入到着色器.
//volume texture ID
GLuint textureID;
bool LoadVolume() {
GenerateVolume(Data, Dim);//生成原始体数据
Classify(CData, Data, Dim);//对体数据分类赋予对应颜色值和不透明度
//generate OpenGL texture
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_3D, textureID);
// set the texture parameters
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
//set the mipmap levels (base and max)
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, 4);
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 200, 200, 200, 0, GL_RGBA, GL_UNSIGNED_BYTE, CData);//将体数据存入3D纹理
GL_CHECK_ERRORS
//generate mipmaps
glGenerateMipmap(GL_TEXTURE_3D);
return true;
}
将体数据存入3D纹理后就可以在着色器中接收此3D纹理,然后利用坐标采样获得采样点的rgb和不透明度.
接下来如果想要在着色器中实现光线投射算法,就需要能够根据位置坐标获得3D纹理对应坐标的颜色值和不透明度。一个不错的解决办法是构造一个1x1x1的单位立方体包围盒,将3D纹理坐标(范围0~1)映射与立方体坐标对应上,也就是映射。这样就可以通过着色器的内置变量片元坐标position,获取到对应位置坐标的3d纹理颜色和不透明度,然后沿着光线投射方向步进采样合成颜色值.
以下是相关的代码,省略了绘制立方体的部分。由于这里我绘制的立方体是xyz值范围(-0.5~0.5)的立方体,所以要与3D纹理坐标映射,需要做一个变换,即用于采样3D纹理的坐标vUV需要由片元位置坐标加上0.5得到,下面是完成这个映射过程的顶点着色器代码.
#version 330 core
layout(location = 0) in vec3 vVertex; //object space vertex position
//uniform
uniform mat4 MVP;
smooth out vec3 vUV;
void main()
{
gl_Position = MVP*vec4(vVertex.xyz,1);
vUV = vVertex + vec3(0.5);
}
下面是片元着色器的代码,实现了光线投射算法。需要注意的是步进起始点,当视点位于体渲染外的时候,起始点是着色器内部获取的立方体表面的坐标,这是正确的,而当视点移动到体渲染对象的内部观察时,这个起始点就不对了,这时的起始点不应该在立方体表面,而应该以视点作为起始点往视线方向采样,如下图所示。所以需要加一个判断条件,判断视点是否在立方体包围盒内部.
下面是完整的片元着色器代码 。
#version 330 core
layout(location = 0) out vec4 vFragColor; //fragment shader output
smooth in vec3 vUV; //用于采样3D纹理的坐标
uniform sampler3D volume; //体数据纹理
uniform vec3 camPos; //相机位置
uniform vec3 step_size; //采样步长
//constants
const int MAX_SAMPLES = 300;
const vec3 texMin = vec3(0); //最小纹理坐标
const vec3 texMax = vec3(1); //最大纹理坐标
void main()
{
vec3 dataPos = vUV; //光线投射起始点坐标
vec3 geomDir; //光线步进方向
if(abs(camPos.x)<=0.5&&abs(camPos.y)<=0.5&&abs(camPos.z)<=0.5)//当相机也就是视点位于体渲染对象内部时,起始点应该改为相机视点的位置坐标作为起始点
{
dataPos=camPos+vec3(0.5);
}
geomDir = normalize((vUV-vec3(0.5)) - camPos); //由视点坐标和起始点坐标相减得到沿视线方向步进的方向的向量
vec3 dirStep = geomDir * step_size;
bool stop = false;
vec4 cumc=vec4(0);
//沿射线方向采样累积颜色和不透明度
for (int i = 0; i < MAX_SAMPLES; i++) {
dataPos = dataPos + dirStep;
stop = dot(sign(dataPos-texMin),sign(texMax-dataPos)) < 3.0;
if (stop)
break;
vec4 samplec=texture(volume, dataPos).rgba;//获取采样点颜色值和不透明度
cumc[0]+=samplec.r*samplec[3]*(1-cumc[3]);
cumc[1]+=samplec.g*samplec[3]*(1-cumc[3]);
cumc[2]+=samplec.b*samplec[3]*(1-cumc[3]);
cumc[3]+=samplec.a*(1-cumc[3]);
if( cumc[3]>0.99)
break;
}
vFragColor=cumc.rgba;
}
最终实现的效果如图所示,为了方便调试,利用imgui添加了一个简单的GUI界面 。
这个案例应该会对理解体渲染和GPU实现体渲染有所帮助.
最后此篇关于OpenGL实现GPU体渲染的文章就讲到这里了,如果你想了解更多关于OpenGL实现GPU体渲染的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
在 OpenGL/ES 中,在实现渲染到纹理功能时,您必须小心,不要引起反馈循环(从正在写入的同一纹理中读取像素)。由于显而易见的原因,当您读取和写入纹理的相同像素时,行为是未定义的。但是,如果您正在
正如我们最终都知道的那样,规范是一回事,实现是另一回事。大多数错误是我们自己造成的,但有时情况并非如此。 我相信列出以下内容会很有用: GPU 驱动程序中当前已知的与最新版本的 OpenGL 和 GL
很难说出这里问的是什么。这个问题是模棱两可的、模糊的、不完整的、过于宽泛的或修辞的,无法以目前的形式得到合理的回答。为了帮助澄清这个问题以便可以重新打开它,visit the help center
我正在学习 OpenGL,非常想知道与显卡的交互如何。 我觉得了解它是如何在图形驱动程序中实现的,会让我了解 opengl 的完整内部结构(通过这个我可以知道哪些阶段/因素影响我对 opengl 性能
我正在尝试绘制到大于屏幕尺寸(即 320x480)的渲染缓冲区 (512x512)。 执行 glReadPixels 后,图像看起来是正确的,除非图像的尺寸超过屏幕尺寸——在本例中,超过 320 水平
我正在 Windows 中制作一个 3D 小行星游戏(使用 OpenGL 和 GLUT),您可以在其中穿过一堆障碍物在太空中移动并生存下来。我正在寻找一种方法来针对无聊的 bg 颜色选项设置图像背景。
如果我想要一个包含 100 个 10*10 像素 Sprite 的 Sprite 表,是否可以将它们全部排成一排来制作 1,000*10 像素纹理?还是 GPU 对不那么窄的纹理表现更好?这对性能有什
这个问题在这里已经有了答案: Rendering 2D sprites in a 3D world? (7 个答案) 关闭 6 年前。 我如何概念化让图像始终面对相机。我尝试将三角函数与 arcta
是否可以在 OpenGL 中增加缓冲区? 假设我想使用实例化渲染。每次在世界上生成一个新对象时,我都必须用实例化数据更新缓冲区。 在这种情况下,我有一个 3 个 float 的缓冲区 std::v
有人可以向我解释为什么下面的代码没有绘制任何东西,但如果我使用 GL_LINE_LOOP 它确实形成了一个闭环吗? glBegin(GL_POLYGON); for(int i = 0; i <= N
正如标题所说,OpenGL 中的渲染目标是什么?我对 OpenGL 很陌生,我看到的所有网站都让我很困惑。 它只是一个缓冲区,我在其中放置稍后将用于渲染的东西吗? 如果您能提供一个很好的引用来阅读它,
当使用 OpenGL 1.4 固定功能多纹理时,每个纹理阶段的输出在传递到下一个阶段之前是否都固定在 [0, 1]? spec说(第 153 页): If the value of TEXTURE_E
我比较了 2 个函数 openGL ES 和 openGL gvec4 texelFetchOffset(gsampler2DArray sampler, ivec3 P, int lod, ivec
关闭。这个问题是off-topic .它目前不接受答案。 想改进这个问题吗? Update the question所以它是on-topic用于堆栈溢出。 关闭 10 年前。 Improve thi
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 想改进这个问题?将问题更新为 on-topic对于堆栈溢出。 6年前关闭。 Improve this qu
那么当你调用opengl函数时,比如glDraw或者gLBufferData,是否会导致程序线程停止等待GL完成调用呢? 如果不是,那么 GL 如何处理调用像 glDraw 这样的重要函数,然后立即更
我正在尝试实现级联阴影贴图,当我想访问我的视锥体的每个分区的相应深度纹理时,我遇到了一个错误。 更具体地说,当我想选择正确的阴影纹理时会出现我的问题,如果我尝试下面的代码,我会得到一个像 this 中
我想为OpenGL ES和OpenGL(Windows)使用相同的着色器源。为此,我想定义自定义数据类型并仅使用OpenGL ES函数。 一种方法是定义: #define highp #define
我尝试用 6 个位图映射立方体以实现天空盒效果。我的问题是一个纹理映射到立方体的每个面。我已经检查了 gDEBugger,在立方体纹理内存中我只有一个 图像(因为我尝试加载六个图像)。 代码准备纹理:
在 OpenGL 中偏移深度的最佳方法是什么?我目前每个多边形都有索引顶点属性,我将其传递给 OpenGL 中的顶点着色器。我的目标是在深度上偏移多边形,其中最高索引始终位于较低索引的前面。我目前有这
我是一名优秀的程序员,十分优秀!