有没有一种简单的方法可以在 OpenGL (JOGL) 中获取对象的深度
Is there a simple way to get the depth of an object in OpenGL (JOGL)
当我点击一个对象时,如何在 3D-space 中获取对象的 z 坐标。
(它实际上不是一个对象,而是一个图形,我需要知道用户选择了什么)我使用 JOGL。
我刚完成移植picking sample from g-truck ogl-samples。
我会尽量给你一个关于代码的快速解释。
我们首先启用深度测试
private boolean initTest(GL4 gl4) {
gl4.glEnable(GL_DEPTH_TEST);
return true;
}
在initBuffer
我们:
- 用
glGenBuffers
生成我们需要的所有缓冲区
- 绑定元素缓冲区,然后我们传输索引的内容。每个索引指的是要使用的顶点。我们需要先绑定它,因为
glBufferData
将使用第一个参数指定的目标范围内的任何内容,在本例中为 GL_ELEMENT_ARRAY_BUFFER
- 对顶点本身做同样的事情。
- 获取
GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT
(它是一个全局参数)以确定存储我们的变换变量的最小统一块大小。如果我们想通过 glBindBufferRange
绑定它,这是必要的,我们不会使用该函数来绑定我们的拾取缓冲区,这就是为什么我们只传递一个浮点数的大小,Float.BYTES
glBufferData
的最后一个参数只是一个提示(这取决于 OpenGL 和驱动程序做他们想做的事),如您所见,索引和顶点是静态的,因为我们不会改变它们不再,但对于统一缓冲区是动态的,因为我们将在每一帧更新它们。
代码:
private boolean initBuffer(GL4 gl4) {
gl4.glGenBuffers(Buffer.MAX.ordinal(), bufferName, 0);
gl4.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferName[Buffer.ELEMENT.ordinal()]);
ShortBuffer elementBuffer = GLBuffers.newDirectShortBuffer(elementData);
gl4.glBufferData(GL_ELEMENT_ARRAY_BUFFER, elementSize, elementBuffer, GL_STATIC_DRAW);
gl4.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
gl4.glBindBuffer(GL_ARRAY_BUFFER, bufferName[Buffer.VERTEX.ordinal()]);
FloatBuffer vertexBuffer = GLBuffers.newDirectFloatBuffer(vertexData);
gl4.glBufferData(GL_ARRAY_BUFFER, vertexSize, vertexBuffer, GL_STATIC_DRAW);
gl4.glBindBuffer(GL_ARRAY_BUFFER, 0);
int[] uniformBufferOffset = {0};
gl4.glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, uniformBufferOffset, 0);
int uniformBlockSize = Math.max(projection.length * Float.BYTES, uniformBufferOffset[0]);
gl4.glBindBuffer(GL_UNIFORM_BUFFER, bufferName[Buffer.TRANSFORM.ordinal()]);
gl4.glBufferData(GL_UNIFORM_BUFFER, uniformBlockSize, null, GL_DYNAMIC_DRAW);
gl4.glBindBuffer(GL_UNIFORM_BUFFER, 0);
gl4.glBindBuffer(GL_TEXTURE_BUFFER, bufferName[Buffer.PICKING.ordinal()]);
gl4.glBufferData(GL_TEXTURE_BUFFER, Float.BYTES, null, GL_DYNAMIC_READ);
gl4.glBindBuffer(GL_TEXTURE_BUFFER, 0);
return true;
}
在initTexture
我们初始化我们的纹理,我们:
- 使用
glGenTextures
生成两个纹理
- 将
GL_UNPACK_ALIGNMENT
设置为 1(默认通常为 4 个字节),以避免任何问题,(因为您的水平纹理大小必须与对齐方式匹配)。
- 将 activeTexture 设置为
GL_TEXTURE0
,有特定数量的纹理插槽,您需要在处理任何纹理之前指定它。
- 绑定漫反射纹理
- 设置 swizzle,这是每个频道将收到的内容
- 设置级别 (mipmap),其中 0 是基础 (original/biggest)
- 设置过滤器
- 分配 space,级别包含在
glTexStorage2D
中
- 每一关传送对应的数据
- 重置
GL_UNPACK_ALIGNMENT
- 绑定到
GL_TEXTURE0
我们的其他纹理 PICKING
- 分配单个 32b 浮点存储并将
PICKING
纹理关联到具有 glTexBuffer
的 PICKING
缓冲区
代码:
private boolean initTexture(GL4 gl4) {
try {
jgli.Texture2D texture = new Texture2D(jgli.Load.load(TEXTURE_ROOT + "/" + TEXTURE_DIFFUSE));
jgli.Gl.Format format = jgli.Gl.instance.translate(texture.format());
gl4.glGenTextures(Texture.MAX.ordinal(), textureName, 0);
// Diffuse
{
gl4.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
gl4.glActiveTexture(GL_TEXTURE0);
gl4.glBindTexture(GL_TEXTURE_2D, textureName[Texture.DIFFUSE.ordinal()]);
gl4.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED);
gl4.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_GREEN);
gl4.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_BLUE);
gl4.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ALPHA);
gl4.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
gl4.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, texture.levels() - 1);
gl4.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
gl4.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gl4.glTexStorage2D(GL_TEXTURE_2D, texture.levels(), format.internal.value,
texture.dimensions(0)[0], texture.dimensions(0)[1]);
for (int level = 0; level < texture.levels(); ++level) {
gl4.glTexSubImage2D(GL_TEXTURE_2D, level,
0, 0,
texture.dimensions(level)[0], texture.dimensions(level)[1],
format.external.value, format.type.value,
texture.data(0, 0, level));
}
gl4.glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
}
// Piking
{
gl4.glBindTexture(GL_TEXTURE_BUFFER, textureName[Texture.PICKING.ordinal()]);
gl4.glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, bufferName[Buffer.PICKING.ordinal()]);
gl4.glBindTexture(GL_TEXTURE_BUFFER, 0);
}
} catch (IOException ex) {
Logger.getLogger(Gl_420_picking.class.getName()).log(Level.SEVERE, null, ex);
}
return true;
}
在initProgram
我们初始化我们的程序,通过:
- 正在生成管道(不同着色器的组合),
glGenProgramPipelines
- 创建顶点着色器代码
vertShaderCode
,其中GL_VERTEX_SHADER
是着色器类型,SHADERS_ROOT
是着色器源码所在的地方,SHADERS_SOURCE_UPDATE
是名称"vert"
是扩展名。
- 初始化它,与片段着色器类似
- 抓取生成的索引并保存在
programName
- 将程序设置为可分离的,这里没什么用,只是纯粹的运动,
glProgramParameteri
- 将两个着色器添加到我们的
shaderProgram
并链接和编译它,link
- 指定我们的
pipelineName
有哪个程序阶段,glUseProgramStages
代码:
private boolean initProgram(GL4 gl4) {
boolean validated = true;
gl4.glGenProgramPipelines(1, pipelineName, 0);
// Create program
if (validated) {
ShaderProgram shaderProgram = new ShaderProgram();
ShaderCode vertShaderCode = ShaderCode.create(gl4, GL_VERTEX_SHADER,
this.getClass(), SHADERS_ROOT, null, SHADERS_SOURCE_UPDATE, "vert", null, true);
ShaderCode fragShaderCode = ShaderCode.create(gl4, GL_FRAGMENT_SHADER,
this.getClass(), SHADERS_ROOT, null, SHADERS_SOURCE_UPDATE, "frag", null, true);
shaderProgram.init(gl4);
programName = shaderProgram.program();
gl4.glProgramParameteri(programName, GL_PROGRAM_SEPARABLE, GL_TRUE);
shaderProgram.add(vertShaderCode);
shaderProgram.add(fragShaderCode);
shaderProgram.link(gl4, System.out);
}
if (validated) {
gl4.glUseProgramStages(pipelineName[0], GL_VERTEX_SHADER_BIT | GL_FRAGMENT_SHADER_BIT, programName);
}
return validated & checkError(gl4, "initProgram");
}
在initVertexArray
我们:
- 生成单个顶点数组,
glGenVertexArrays
,并绑定它,glBindVertexArray
- 绑定顶点缓冲区并设置位置和颜色的属性,这里是交错的。该位置由属性索引
Semantic.Attr.POSITION
(这将匹配顶点着色器中的索引)、组件大小 2
、类型 GL_FLOAT
、归一化 false
、步幅或每个顶点属性的总大小 2 * 2 * Float.BYTES
和该属性中的偏移量 0
。颜色也是如此。
- 取消绑定顶点缓冲区,因为它不是顶点数组状态的一部分。它必须仅为
glVertexAttribPointer
绑定,以便 OpenGL 可以知道这些参数指的是哪个缓冲区。
- 启用对应的顶点属性数组,
glEnableVertexAttribArray
- 绑定元素(索引)数组,顶点数组的一部分
代码:
private boolean initVertexArray(GL4 gl4) {
gl4.glGenVertexArrays(1, vertexArrayName, 0);
gl4.glBindVertexArray(vertexArrayName[0]);
{
gl4.glBindBuffer(GL_ARRAY_BUFFER, bufferName[Buffer.VERTEX.ordinal()]);
gl4.glVertexAttribPointer(Semantic.Attr.POSITION, 2, GL_FLOAT, false, 2 * 2 * Float.BYTES, 0);
gl4.glVertexAttribPointer(Semantic.Attr.TEXCOORD, 2, GL_FLOAT, false, 2 * 2 * Float.BYTES, 2 * Float.BYTES);
gl4.glBindBuffer(GL_ARRAY_BUFFER, 0);
gl4.glEnableVertexAttribArray(Semantic.Attr.POSITION);
gl4.glEnableVertexAttribArray(Semantic.Attr.TEXCOORD);
gl4.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferName[Buffer.ELEMENT.ordinal()]);
}
gl4.glBindVertexArray(0);
return true;
}
在render
我们:
- 绑定将包含我们的转换矩阵的
TRANSFORM
缓冲区。
- 从中得到一个 byteBuffer
pointer
。
- 计算投影、视图和模型矩阵并以相同的顺序将它们相乘 p * v * m,也称为 mvp 矩阵。
- 将我们的 mvp 矩阵保存在
pointer
中并倒回缓冲区(位置再次设置为 0)。
- 取消映射以确保它被上传到 gpu
- 设置视口以匹配我们的 window 尺寸
- 将清除深度值设置为 1(多余,因为它是默认值),清除深度,
depthValue
,颜色缓冲区,颜色 {1.0f, 0.5f, 0.0f, 1.0f}
- 绑定管道
- 设置活动纹理0
- 绑定漫反射纹理和拾取图像纹理
- 绑定顶点数组
- 绑定变换统一缓冲区
- render,
glDrawElementsInstancedBaseVertexBaseInstance
被过度使用了,但重要的是原始类型GL_TRIANGLES
,索引的数量elementCount
和它们的类型GL_UNSIGNED_SHORT
- 绑定拾取纹理缓冲区并检索其值
代码:
@Override
protected boolean render(GL gl) {
GL4 gl4 = (GL4) gl;
{
gl4.glBindBuffer(GL_UNIFORM_BUFFER, bufferName[Buffer.TRANSFORM.ordinal()]);
ByteBuffer pointer = gl4.glMapBufferRange(
GL_UNIFORM_BUFFER, 0, projection.length * Float.BYTES,
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
FloatUtil.makePerspective(projection, 0, true, (float) Math.PI * 0.25f,
(float) windowSize.x / windowSize.y, 0.1f, 100.0f);
FloatUtil.makeIdentity(model);
FloatUtil.multMatrix(projection, view());
FloatUtil.multMatrix(projection, model);
for (float f : projection) {
pointer.putFloat(f);
}
pointer.rewind();
// Make sure the uniform buffer is uploaded
gl4.glUnmapBuffer(GL_UNIFORM_BUFFER);
}
gl4.glViewportIndexedf(0, 0, 0, windowSize.x, windowSize.y);
float[] depthValue = {1.0f};
gl4.glClearBufferfv(GL_DEPTH, 0, depthValue, 0);
gl4.glClearBufferfv(GL_COLOR, 0, new float[]{1.0f, 0.5f, 0.0f, 1.0f}, 0);
gl4.glBindProgramPipeline(pipelineName[0]);
gl4.glActiveTexture(GL_TEXTURE0);
gl4.glBindTexture(GL_TEXTURE_2D, textureName[Texture.DIFFUSE.ordinal()]);
gl4.glBindImageTexture(Semantic.Image.PICKING, textureName[Texture.PICKING.ordinal()],
0, false, 0, GL_WRITE_ONLY, GL_R32F);
gl4.glBindVertexArray(vertexArrayName[0]);
gl4.glBindBufferBase(GL_UNIFORM_BUFFER, Semantic.Uniform.TRANSFORM0, bufferName[Buffer.TRANSFORM.ordinal()]);
gl4.glDrawElementsInstancedBaseVertexBaseInstance(GL_TRIANGLES, elementCount, GL_UNSIGNED_SHORT, 0, 5, 0, 0);
gl4.glBindBuffer(GL_ARRAY_BUFFER, bufferName[Buffer.PICKING.ordinal()]);
ByteBuffer pointer = gl4.glMapBufferRange(GL_ARRAY_BUFFER, 0, Float.BYTES, GL_MAP_READ_BIT);
float depth = pointer.getFloat();
gl4.glUnmapBuffer(GL_ARRAY_BUFFER);
System.out.printf("Depth: %2.3f\n", depth);
return true;
}
在我们的顶点着色器中,为每个顶点执行,我们:
- 定义 glsl 版本和配置文件
- 定义所有的属性索引,必须与我们之前使用的
Semantic
一致
- 设置一些内存布局参数,如std140和column_mayor(无用,矩阵默认值)
- 声明
Transform
统一缓冲区
- 声明一个 vec3 位置和 vec2 texCoord 输入
- 声明一个(内置,不完整且无用)gl_PerVertex输出
- 声明一个
Block
块输出
- 在我们的块内保存传入的
texCoord
并在 gl_Position
内保存我们在剪辑 space 位置的顶点。传入的position
顶点在模型space中 -> *模型矩阵=顶点在世界space,*view/camera矩阵=顶点在Camera/Viewspace, * 投影矩阵 = Clip 中的顶点 space.
代码:
#version 420 core
#define POSITION 0
#define COLOR 3
#define TEXCOORD 4
#define TRANSFORM0 1
precision highp float;
precision highp int;
layout(std140, column_major) uniform;
layout(binding = TRANSFORM0) uniform Transform
{
mat4 mvp;
} transform;
layout(location = POSITION) in vec3 position;
layout(location = TEXCOORD) in vec2 texCoord;
out gl_PerVertex
{
vec4 gl_Position;
};
out Block
{
vec2 texCoord;
} outBlock;
void main()
{
outBlock.texCoord = texCoord;
gl_Position = transform.mvp * vec4(position, 1.0);
}
顶点着色器之后可能还有其他阶段,例如曲面细分 control/evaluation 和几何,但它们不是强制性的。
最后一个阶段是片段着色器,每 fragment/pixel 执行一次,启动方式类似,然后我们:
- 在
binding 0
上声明纹理 diffuse
,它与 render
中的 glActiveTexture(GL_TEXTURE0)
和我们将保存深度的 imageBuffer picking
相匹配由 binding 1
标识,与 render.glBindImageTexture
中的 Semantic.Image.PICKING
匹配
- 声明拾取坐标,这里是硬编码的,但没有什么能阻止你把它们变成统一变量并在运行时设置它
- 声明传入的
Block
块保存纹理坐标
- 声明默认输出
color
- 如果当前片段坐标
gl_FragCoord
(内置函数)对应拾取坐标pickingCoord
,将当前z值gl_FragCoord.z
保存在imageBuffer中depth
并且将输出 color
设置为 vec4(1, 0, 1, 1)
,否则我们通过 texture(diffuse, inBlock.texCoord.st)
将其设置为等于漫反射纹理。 st
是stqp选择的一部分,xywz或rgba的同义词。
代码:
#version 420 core
#define FRAG_COLOR 0
precision highp float;
precision highp int;
layout(std140, column_major) uniform;
in vec4 gl_FragCoord;
layout(binding = 0) uniform sampler2D diffuse;
layout(binding = 1, r32f) writeonly uniform imageBuffer depth;
uvec2 pickingCoord = uvec2(320, 240);
in Block
{
vec2 texCoord;
} inBlock;
layout(location = FRAG_COLOR, index = 0) out vec4 color;
void main()
{
if(all(equal(pickingCoord, uvec2(gl_FragCoord.xy))))
{
imageStore(depth, 0, vec4(gl_FragCoord.z, 0, 0, 0));
color = vec4(1, 0, 1, 1);
}
else
color = texture(diffuse, inBlock.texCoord.st);
}
最后,在 end
中,我们清理了所有 OpenGL 资源:
@Override
protected boolean end(GL gl) {
GL4 gl4 = (GL4) gl;
gl4.glDeleteProgramPipelines(1, pipelineName, 0);
gl4.glDeleteProgram(programName);
gl4.glDeleteBuffers(Buffer.MAX.ordinal(), bufferName, 0);
gl4.glDeleteTextures(Texture.MAX.ordinal(), textureName, 0);
gl4.glDeleteVertexArrays(1, vertexArrayName, 0);
return true;
}
当我点击一个对象时,如何在 3D-space 中获取对象的 z 坐标。 (它实际上不是一个对象,而是一个图形,我需要知道用户选择了什么)我使用 JOGL。
我刚完成移植picking sample from g-truck ogl-samples。
我会尽量给你一个关于代码的快速解释。
我们首先启用深度测试
private boolean initTest(GL4 gl4) {
gl4.glEnable(GL_DEPTH_TEST);
return true;
}
在initBuffer
我们:
- 用
glGenBuffers
生成我们需要的所有缓冲区
- 绑定元素缓冲区,然后我们传输索引的内容。每个索引指的是要使用的顶点。我们需要先绑定它,因为
glBufferData
将使用第一个参数指定的目标范围内的任何内容,在本例中为GL_ELEMENT_ARRAY_BUFFER
- 对顶点本身做同样的事情。
- 获取
GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT
(它是一个全局参数)以确定存储我们的变换变量的最小统一块大小。如果我们想通过glBindBufferRange
绑定它,这是必要的,我们不会使用该函数来绑定我们的拾取缓冲区,这就是为什么我们只传递一个浮点数的大小,Float.BYTES
glBufferData
的最后一个参数只是一个提示(这取决于 OpenGL 和驱动程序做他们想做的事),如您所见,索引和顶点是静态的,因为我们不会改变它们不再,但对于统一缓冲区是动态的,因为我们将在每一帧更新它们。
代码:
private boolean initBuffer(GL4 gl4) {
gl4.glGenBuffers(Buffer.MAX.ordinal(), bufferName, 0);
gl4.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferName[Buffer.ELEMENT.ordinal()]);
ShortBuffer elementBuffer = GLBuffers.newDirectShortBuffer(elementData);
gl4.glBufferData(GL_ELEMENT_ARRAY_BUFFER, elementSize, elementBuffer, GL_STATIC_DRAW);
gl4.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
gl4.glBindBuffer(GL_ARRAY_BUFFER, bufferName[Buffer.VERTEX.ordinal()]);
FloatBuffer vertexBuffer = GLBuffers.newDirectFloatBuffer(vertexData);
gl4.glBufferData(GL_ARRAY_BUFFER, vertexSize, vertexBuffer, GL_STATIC_DRAW);
gl4.glBindBuffer(GL_ARRAY_BUFFER, 0);
int[] uniformBufferOffset = {0};
gl4.glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, uniformBufferOffset, 0);
int uniformBlockSize = Math.max(projection.length * Float.BYTES, uniformBufferOffset[0]);
gl4.glBindBuffer(GL_UNIFORM_BUFFER, bufferName[Buffer.TRANSFORM.ordinal()]);
gl4.glBufferData(GL_UNIFORM_BUFFER, uniformBlockSize, null, GL_DYNAMIC_DRAW);
gl4.glBindBuffer(GL_UNIFORM_BUFFER, 0);
gl4.glBindBuffer(GL_TEXTURE_BUFFER, bufferName[Buffer.PICKING.ordinal()]);
gl4.glBufferData(GL_TEXTURE_BUFFER, Float.BYTES, null, GL_DYNAMIC_READ);
gl4.glBindBuffer(GL_TEXTURE_BUFFER, 0);
return true;
}
在initTexture
我们初始化我们的纹理,我们:
- 使用
glGenTextures
生成两个纹理
- 将
GL_UNPACK_ALIGNMENT
设置为 1(默认通常为 4 个字节),以避免任何问题,(因为您的水平纹理大小必须与对齐方式匹配)。 - 将 activeTexture 设置为
GL_TEXTURE0
,有特定数量的纹理插槽,您需要在处理任何纹理之前指定它。 - 绑定漫反射纹理
- 设置 swizzle,这是每个频道将收到的内容
- 设置级别 (mipmap),其中 0 是基础 (original/biggest)
- 设置过滤器
- 分配 space,级别包含在
glTexStorage2D
中
- 每一关传送对应的数据
- 重置
GL_UNPACK_ALIGNMENT
- 绑定到
GL_TEXTURE0
我们的其他纹理PICKING
- 分配单个 32b 浮点存储并将
PICKING
纹理关联到具有glTexBuffer
的
PICKING
缓冲区
代码:
private boolean initTexture(GL4 gl4) {
try {
jgli.Texture2D texture = new Texture2D(jgli.Load.load(TEXTURE_ROOT + "/" + TEXTURE_DIFFUSE));
jgli.Gl.Format format = jgli.Gl.instance.translate(texture.format());
gl4.glGenTextures(Texture.MAX.ordinal(), textureName, 0);
// Diffuse
{
gl4.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
gl4.glActiveTexture(GL_TEXTURE0);
gl4.glBindTexture(GL_TEXTURE_2D, textureName[Texture.DIFFUSE.ordinal()]);
gl4.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED);
gl4.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_GREEN);
gl4.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_BLUE);
gl4.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ALPHA);
gl4.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
gl4.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, texture.levels() - 1);
gl4.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
gl4.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gl4.glTexStorage2D(GL_TEXTURE_2D, texture.levels(), format.internal.value,
texture.dimensions(0)[0], texture.dimensions(0)[1]);
for (int level = 0; level < texture.levels(); ++level) {
gl4.glTexSubImage2D(GL_TEXTURE_2D, level,
0, 0,
texture.dimensions(level)[0], texture.dimensions(level)[1],
format.external.value, format.type.value,
texture.data(0, 0, level));
}
gl4.glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
}
// Piking
{
gl4.glBindTexture(GL_TEXTURE_BUFFER, textureName[Texture.PICKING.ordinal()]);
gl4.glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, bufferName[Buffer.PICKING.ordinal()]);
gl4.glBindTexture(GL_TEXTURE_BUFFER, 0);
}
} catch (IOException ex) {
Logger.getLogger(Gl_420_picking.class.getName()).log(Level.SEVERE, null, ex);
}
return true;
}
在initProgram
我们初始化我们的程序,通过:
- 正在生成管道(不同着色器的组合),
glGenProgramPipelines
- 创建顶点着色器代码
vertShaderCode
,其中GL_VERTEX_SHADER
是着色器类型,SHADERS_ROOT
是着色器源码所在的地方,SHADERS_SOURCE_UPDATE
是名称"vert"
是扩展名。 - 初始化它,与片段着色器类似
- 抓取生成的索引并保存在
programName
- 将程序设置为可分离的,这里没什么用,只是纯粹的运动,
glProgramParameteri
- 将两个着色器添加到我们的
shaderProgram
并链接和编译它,link
- 指定我们的
pipelineName
有哪个程序阶段,glUseProgramStages
代码:
private boolean initProgram(GL4 gl4) {
boolean validated = true;
gl4.glGenProgramPipelines(1, pipelineName, 0);
// Create program
if (validated) {
ShaderProgram shaderProgram = new ShaderProgram();
ShaderCode vertShaderCode = ShaderCode.create(gl4, GL_VERTEX_SHADER,
this.getClass(), SHADERS_ROOT, null, SHADERS_SOURCE_UPDATE, "vert", null, true);
ShaderCode fragShaderCode = ShaderCode.create(gl4, GL_FRAGMENT_SHADER,
this.getClass(), SHADERS_ROOT, null, SHADERS_SOURCE_UPDATE, "frag", null, true);
shaderProgram.init(gl4);
programName = shaderProgram.program();
gl4.glProgramParameteri(programName, GL_PROGRAM_SEPARABLE, GL_TRUE);
shaderProgram.add(vertShaderCode);
shaderProgram.add(fragShaderCode);
shaderProgram.link(gl4, System.out);
}
if (validated) {
gl4.glUseProgramStages(pipelineName[0], GL_VERTEX_SHADER_BIT | GL_FRAGMENT_SHADER_BIT, programName);
}
return validated & checkError(gl4, "initProgram");
}
在initVertexArray
我们:
- 生成单个顶点数组,
glGenVertexArrays
,并绑定它,glBindVertexArray
- 绑定顶点缓冲区并设置位置和颜色的属性,这里是交错的。该位置由属性索引
Semantic.Attr.POSITION
(这将匹配顶点着色器中的索引)、组件大小2
、类型GL_FLOAT
、归一化false
、步幅或每个顶点属性的总大小2 * 2 * Float.BYTES
和该属性中的偏移量0
。颜色也是如此。 - 取消绑定顶点缓冲区,因为它不是顶点数组状态的一部分。它必须仅为
glVertexAttribPointer
绑定,以便 OpenGL 可以知道这些参数指的是哪个缓冲区。 - 启用对应的顶点属性数组,
glEnableVertexAttribArray
- 绑定元素(索引)数组,顶点数组的一部分
代码:
private boolean initVertexArray(GL4 gl4) {
gl4.glGenVertexArrays(1, vertexArrayName, 0);
gl4.glBindVertexArray(vertexArrayName[0]);
{
gl4.glBindBuffer(GL_ARRAY_BUFFER, bufferName[Buffer.VERTEX.ordinal()]);
gl4.glVertexAttribPointer(Semantic.Attr.POSITION, 2, GL_FLOAT, false, 2 * 2 * Float.BYTES, 0);
gl4.glVertexAttribPointer(Semantic.Attr.TEXCOORD, 2, GL_FLOAT, false, 2 * 2 * Float.BYTES, 2 * Float.BYTES);
gl4.glBindBuffer(GL_ARRAY_BUFFER, 0);
gl4.glEnableVertexAttribArray(Semantic.Attr.POSITION);
gl4.glEnableVertexAttribArray(Semantic.Attr.TEXCOORD);
gl4.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferName[Buffer.ELEMENT.ordinal()]);
}
gl4.glBindVertexArray(0);
return true;
}
在render
我们:
- 绑定将包含我们的转换矩阵的
TRANSFORM
缓冲区。 - 从中得到一个 byteBuffer
pointer
。 - 计算投影、视图和模型矩阵并以相同的顺序将它们相乘 p * v * m,也称为 mvp 矩阵。
- 将我们的 mvp 矩阵保存在
pointer
中并倒回缓冲区(位置再次设置为 0)。 - 取消映射以确保它被上传到 gpu
- 设置视口以匹配我们的 window 尺寸
- 将清除深度值设置为 1(多余,因为它是默认值),清除深度,
depthValue
,颜色缓冲区,颜色{1.0f, 0.5f, 0.0f, 1.0f}
- 绑定管道
- 设置活动纹理0
- 绑定漫反射纹理和拾取图像纹理
- 绑定顶点数组
- 绑定变换统一缓冲区
- render,
glDrawElementsInstancedBaseVertexBaseInstance
被过度使用了,但重要的是原始类型GL_TRIANGLES
,索引的数量elementCount
和它们的类型GL_UNSIGNED_SHORT
- 绑定拾取纹理缓冲区并检索其值
代码:
@Override
protected boolean render(GL gl) {
GL4 gl4 = (GL4) gl;
{
gl4.glBindBuffer(GL_UNIFORM_BUFFER, bufferName[Buffer.TRANSFORM.ordinal()]);
ByteBuffer pointer = gl4.glMapBufferRange(
GL_UNIFORM_BUFFER, 0, projection.length * Float.BYTES,
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
FloatUtil.makePerspective(projection, 0, true, (float) Math.PI * 0.25f,
(float) windowSize.x / windowSize.y, 0.1f, 100.0f);
FloatUtil.makeIdentity(model);
FloatUtil.multMatrix(projection, view());
FloatUtil.multMatrix(projection, model);
for (float f : projection) {
pointer.putFloat(f);
}
pointer.rewind();
// Make sure the uniform buffer is uploaded
gl4.glUnmapBuffer(GL_UNIFORM_BUFFER);
}
gl4.glViewportIndexedf(0, 0, 0, windowSize.x, windowSize.y);
float[] depthValue = {1.0f};
gl4.glClearBufferfv(GL_DEPTH, 0, depthValue, 0);
gl4.glClearBufferfv(GL_COLOR, 0, new float[]{1.0f, 0.5f, 0.0f, 1.0f}, 0);
gl4.glBindProgramPipeline(pipelineName[0]);
gl4.glActiveTexture(GL_TEXTURE0);
gl4.glBindTexture(GL_TEXTURE_2D, textureName[Texture.DIFFUSE.ordinal()]);
gl4.glBindImageTexture(Semantic.Image.PICKING, textureName[Texture.PICKING.ordinal()],
0, false, 0, GL_WRITE_ONLY, GL_R32F);
gl4.glBindVertexArray(vertexArrayName[0]);
gl4.glBindBufferBase(GL_UNIFORM_BUFFER, Semantic.Uniform.TRANSFORM0, bufferName[Buffer.TRANSFORM.ordinal()]);
gl4.glDrawElementsInstancedBaseVertexBaseInstance(GL_TRIANGLES, elementCount, GL_UNSIGNED_SHORT, 0, 5, 0, 0);
gl4.glBindBuffer(GL_ARRAY_BUFFER, bufferName[Buffer.PICKING.ordinal()]);
ByteBuffer pointer = gl4.glMapBufferRange(GL_ARRAY_BUFFER, 0, Float.BYTES, GL_MAP_READ_BIT);
float depth = pointer.getFloat();
gl4.glUnmapBuffer(GL_ARRAY_BUFFER);
System.out.printf("Depth: %2.3f\n", depth);
return true;
}
在我们的顶点着色器中,为每个顶点执行,我们:
- 定义 glsl 版本和配置文件
- 定义所有的属性索引,必须与我们之前使用的
Semantic
一致 - 设置一些内存布局参数,如std140和column_mayor(无用,矩阵默认值)
- 声明
Transform
统一缓冲区 - 声明一个 vec3 位置和 vec2 texCoord 输入
- 声明一个(内置,不完整且无用)gl_PerVertex输出
- 声明一个
Block
块输出 - 在我们的块内保存传入的
texCoord
并在gl_Position
内保存我们在剪辑 space 位置的顶点。传入的position
顶点在模型space中 -> *模型矩阵=顶点在世界space,*view/camera矩阵=顶点在Camera/Viewspace, * 投影矩阵 = Clip 中的顶点 space.
代码:
#version 420 core
#define POSITION 0
#define COLOR 3
#define TEXCOORD 4
#define TRANSFORM0 1
precision highp float;
precision highp int;
layout(std140, column_major) uniform;
layout(binding = TRANSFORM0) uniform Transform
{
mat4 mvp;
} transform;
layout(location = POSITION) in vec3 position;
layout(location = TEXCOORD) in vec2 texCoord;
out gl_PerVertex
{
vec4 gl_Position;
};
out Block
{
vec2 texCoord;
} outBlock;
void main()
{
outBlock.texCoord = texCoord;
gl_Position = transform.mvp * vec4(position, 1.0);
}
顶点着色器之后可能还有其他阶段,例如曲面细分 control/evaluation 和几何,但它们不是强制性的。 最后一个阶段是片段着色器,每 fragment/pixel 执行一次,启动方式类似,然后我们:
- 在
binding 0
上声明纹理diffuse
,它与render
中的glActiveTexture(GL_TEXTURE0)
和我们将保存深度的 imageBufferpicking
相匹配由binding 1
标识,与render.glBindImageTexture
中的 - 声明拾取坐标,这里是硬编码的,但没有什么能阻止你把它们变成统一变量并在运行时设置它
- 声明传入的
Block
块保存纹理坐标 - 声明默认输出
color
- 如果当前片段坐标
gl_FragCoord
(内置函数)对应拾取坐标pickingCoord
,将当前z值gl_FragCoord.z
保存在imageBuffer中depth
并且将输出color
设置为vec4(1, 0, 1, 1)
,否则我们通过texture(diffuse, inBlock.texCoord.st)
将其设置为等于漫反射纹理。st
是stqp选择的一部分,xywz或rgba的同义词。
Semantic.Image.PICKING
匹配
代码:
#version 420 core
#define FRAG_COLOR 0
precision highp float;
precision highp int;
layout(std140, column_major) uniform;
in vec4 gl_FragCoord;
layout(binding = 0) uniform sampler2D diffuse;
layout(binding = 1, r32f) writeonly uniform imageBuffer depth;
uvec2 pickingCoord = uvec2(320, 240);
in Block
{
vec2 texCoord;
} inBlock;
layout(location = FRAG_COLOR, index = 0) out vec4 color;
void main()
{
if(all(equal(pickingCoord, uvec2(gl_FragCoord.xy))))
{
imageStore(depth, 0, vec4(gl_FragCoord.z, 0, 0, 0));
color = vec4(1, 0, 1, 1);
}
else
color = texture(diffuse, inBlock.texCoord.st);
}
最后,在 end
中,我们清理了所有 OpenGL 资源:
@Override
protected boolean end(GL gl) {
GL4 gl4 = (GL4) gl;
gl4.glDeleteProgramPipelines(1, pipelineName, 0);
gl4.glDeleteProgram(programName);
gl4.glDeleteBuffers(Buffer.MAX.ordinal(), bufferName, 0);
gl4.glDeleteTextures(Texture.MAX.ordinal(), textureName, 0);
gl4.glDeleteVertexArrays(1, vertexArrayName, 0);
return true;
}