Bloom

使用OpenGL ES 3.0的绽放效果。

绽放效果:强度从非常弱变为非常强烈。

该应用程序显示了bloom实现。 它绘制了以二维5x5阵列排列的立方体,只有对角线的阵列才绽放。 绽放效果的强度从非常弱的变化到非常强。

绽放效果实现如下:

  • 场景(5x5立方体阵列:对角线上的立方体为白色,其他为蓝色)被绘制到渲染目标。
  • 应该绽放的元素(更明亮的元素为放置在对角线上的立方体)被绘制到缩小尺寸的纹理对象(其中场景的其余部分是黑色)。
  • 来自步骤2的结果纹理是水平模糊的 - 结果存储在纹理中,然后用于垂直模糊。
  • 将存储了垂直和水平模糊图像的纹理(步骤3的结果)和来自步骤1的纹理混合(水平和垂直)并绘制到后缓冲器中。

在渲染过程中混合效果不是恒定的:它从非常弱变为非常强。 这是通过重复步骤3不同的次数(取决于所需的效果强度)来实现的 - 唯一的区别是对于第n次迭代,将生成的第(n-1)个结果作为源 对于水平模糊。 为了使绽放效果更加平滑,我们还使用纹理的连续采样。 来自步骤3的最后两次迭代的结果用于最终组合传递。 这两个纹理的颜色与适当的因子值混合在一起。

除了绽放效果,该应用程序还显示:

  • 矩阵计算(例如用于透视图),
  • 实例化绘图(在屏幕上绘制的每个立方体是同一对象的实例),
  • 照明(模型由定向灯照亮),
  • 渲染成纹理。

Instanced Drawing

在屏幕上绘制的每个立方体都是同一对象的实例。 让我们一步一步地完成整个机制。

假设为负责场景渲染的活动程序对象调用以下所有函数。

我们想绘制一个5x5的二维立方体阵列。 值得一提的是,白色和蓝色立方体仍然是同一物体的实例。

#define NUMBER_OF_CUBES (25)
复制代码

生成立方体形状。 我们希望我们的立方体有点小,以适应屏幕。 在这个简单的模型中,我们可以通过多种方式实现这一目标,但让我们使用最简单的模型 - 使用乘数来缩放立方体。 由于该立方体顶点不会在<-1,-1,-1>到<1,1,1>的范围内扩展,而是根据使用的乘数进行调整。

#define CUBE_SCALAR (0.8f)
复制代码

现在我们需要将生成的信息传输到数组缓冲区对象中。

/* Generate buffer object and fill it with cube vertex coordinates data. */
GL_CHECK(glGenBuffers(1,&sceneRenderingObjects.bufferObjectIdCubeCoords) );
GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER,sceneRenderingObjects.bufferObjectIdCubeCoords) );
GL_CHECK(glBufferData(GL_ARRAY_BUFFER,nOfCubeCoordinates * sizeof(GLfloat),(GLfloat*)cubeCoordinates,GL_STATIC_DRAW) );
复制代码

定义通用顶点属性数据的数组。 确保正确缓冲区对象当前绑定到GL_ARRAY_BUFFER(包含立方体顶点数据的缓冲区对象)。 在我们的应用程序中,我们多次重新绑定它,因此在调用glVertexAttribPointer()之前,我们再次绑定它。 在接下来的一个步骤中,我们将描述sceneRenderingProgramLocationsPtr-> attribCubeVertexCoordinates所代表的内容(将在用于实例化绘图的着色器对象中进行描述)。

/* Cube coordinates are constant during rendering process. Set them now. */
GL_CHECK(glBindBuffer         (GL_ARRAY_BUFFER,sceneRenderingObjects.bufferObjectIdCubeCoords) );
GL_CHECK(glVertexAttribPointer(sceneRenderingProgramLocations.attribCubeVertexCoordinates,NUMBER_OF_COMPONENTS_PER_VERTEX,GL_FLOAT,GL_FALSE,0,NULL) );
复制代码

准备就绪后,我们必须启用顶点属性数组。

GL_CHECK(glEnableVertexAttribArray(sceneRenderingProgramLocations.attribCubeVertexCoordinates) );
复制代码

我们现在准备绘制同一对象的多个实例。 我们使用一个特殊的函数,即glDrawArraysInstanced()

/* Draw scene. */
GL_CHECK(glDrawArraysInstanced(GL_TRIANGLES,0,nOfCubeCoordinates,NUMBER_OF_CUBES) );
复制代码

我们希望立方体以5x5二维阵列排列。 我们必须计算每个立方体的位置。 这是在getCubeLocations()函数中完成的。

static GLfloat* getCubeLocations(GLint   numberOfColumns,GLint   numberOfRows,GLfloat cubeScalar,GLfloat distanceBetweenCubes,GLint*  numberOfCubeLocationCoordinatesPtr)
{ASSERT(numberOfCubeLocationCoordinatesPtr != NULL);const float distance                 = distanceBetweenCubes + 2 * cubeScalar; /* A single cube spreads out from* <-cubeScalar, -cubeScalar, -cubeScalar> to* <cubeScalar,   cubeScalar,  cubeScalar>,* with <0, 0, 0> representing the center of the cube.* We have to enlarge the requested distance between cubes* (2 * cubeScalar) times. */int         index                           = 0;int         numberOfCubeLocationCoordinates = 0;const int   numberOfPointCoordinates        = 2;GLfloat*    result                          = NULL;const float xStart                          = -( float(numberOfColumns - 1) / 2.0f * distance);const float yStart                          = -( float(numberOfRows    - 1) / 2.0f * distance);numberOfCubeLocationCoordinates = numberOfPointCoordinates * numberOfColumns * numberOfRows;result                          = (GLfloat*) malloc(numberOfCubeLocationCoordinates * sizeof(GLfloat) );/* Make sure memory allocation succeeded. */ASSERT(result != NULL);for (int rowIndex = 0; rowIndex < numberOfRows; rowIndex++){for (int columnIndex = 0; columnIndex < numberOfColumns; columnIndex++){result[index++] = xStart + (rowIndex    * distance);result[index++] = yStart + (columnIndex * distance);}}*numberOfCubeLocationCoordinatesPtr = numberOfCubeLocationCoordinates;return result;
}
复制代码

然后将结果传递给uniform缓冲区对象

/* Generate uniform buffer object and fill it with cube positions data. */
GL_CHECK(glGenBuffers(1,&sceneRenderingObjects.bufferObjectIdElementLocations) );
GL_CHECK(glBindBuffer(GL_UNIFORM_BUFFER,sceneRenderingObjects.bufferObjectIdElementLocations) );
GL_CHECK(glBufferData(GL_UNIFORM_BUFFER,nOfCubeLocations * sizeof (GLfloat),cubeLocations,GL_STATIC_DRAW) );
复制代码

然后将其用作程序uniform块数据的源。 在接下来的一个步骤中,我们将描述sceneRenderingProgramLocationsPtr-> uniformBlockCubeProperties代表什么(它将在用于实例化绘图的着色器对象中描述)。

/* Cube locations are constant during rendering process. Set them now. */
GL_CHECK(glUniformBlockBinding(sceneRenderingProgramShaderObjects.programObjectId,sceneRenderingProgramLocations.uniformBlockCubeProperties,0 ) );
GL_CHECK(glBindBufferBase     (GL_UNIFORM_BUFFER,0,sceneRenderingObjects.bufferObjectIdElementLocations) );
复制代码

Shader Objects used for Instanced Drawing

顶点着色器源:

#version 300 es
precision mediump float;
#define NUMBER_OF_CUBES (25)
const int is_diagonal_cube[NUMBER_OF_CUBES] = int[NUMBER_OF_CUBES](1, 0, 0, 0, 1,0, 1, 0, 1, 0,0, 0, 1, 0, 0,0, 1, 0, 1, 0,1, 0, 0, 0, 1);
/* UNIFORMS */
uniform mat4 mv_matrix;
uniform mat4 mvp_matrix;
uniform      cube_properties
{vec2 locations[NUMBER_OF_CUBES];
};
/* ATTRIBUTES */
in vec3 cube_vertex_coordinates;
in vec3 cube_vertex_normals;
/* OUTPUTS */out vec3 normal;out vec4 vertex;
flat out int  is_cube_placed_on_diagonal;
void main()
{/* Prepare translation matrix. */mat4 cube_location_matrix = mat4(1.0,                        0.0,                        0.0, 0.0,0.0,                        1.0,                        0.0, 0.0,0.0,                        0.0,                        1.0, 0.0,locations[gl_InstanceID].x, locations[gl_InstanceID].y, 0.0, 1.0);/* Calculate matrices. */mat4 model_view_matrix            = mv_matrix  * cube_location_matrix;mat4 model_view_projection_matrix = mvp_matrix * cube_location_matrix;/* Set output values. */is_cube_placed_on_diagonal = is_diagonal_cube[gl_InstanceID];normal                     = vec3(model_view_matrix * vec4(cube_vertex_normals, 0.0)).xyz;vertex                     = model_view_matrix * vec4(cube_vertex_coordinates, 1.0);/* Set vertex position in NDC space. */gl_Position = model_view_projection_matrix * vec4(cube_vertex_coordinates, 1.0);
}
复制代码

如所见,在顶点着色器中我们使用了cube_vertex_coordinates属性。 并且它的位置应该用作前面步骤之一中描述的glVertexAttribPointer()中的第一个参数。

要获取属性位置,只需调用:

locationsStoragePtr->attribCubeVertexCoordinates = GL_CHECK(glGetAttribLocation(programObjectId, "cube_vertex_coordinates") );
复制代码

还使用了uniform块(cube_properties)。 它的位置应该用作前面步骤之一中描述的glUniformBlockBinding()中的第二个参数。

要获得uniform块位置,只需调用:

locationsStoragePtr->uniformBlockCubeProperties = GL_CHECK(glGetUniformBlockIndex(programObjectId, "cube_properties") );
复制代码

在着色器中,我们有关于每个立方体位置的信息。 根据OpenGL ES着色语言规范,变量gl_InstanceID是一个顶点着色器输入变量,它保存实例化绘制调用中当前图元的实例编号。 我们使用此变量来引用存储在uniform块中的位置数组的必需元素。 然后将该信息用于准备平移矩阵,然后将其用于设置NDC空间中的顶点。

片段着色器源:

#version 300 es
precision lowp float;
#define EPSILON (0.00001)
struct _light_properties
{vec3  ambient;vec3  color;float constant_attenuation;float linear_attenuation;vec3  position;float quadratic_attenauation;float shininess;float strength;
};
/* UNIFORMS */
uniform vec3              camera_position;
uniform _light_properties light_properties;
/* INPUTS */in vec3 normal;in vec4 vertex;
flat in int  is_cube_placed_on_diagonal;
/* OUTPUTS */
/* Stores color data of cubes that should not be bloomed.*/
layout(location = 0) out vec4 normal_scene_color;
/* Stores color data of cubes we want bloomed. */
layout(location = 1) out vec4 bloom_element_color;
void main()
{vec4 vertex_color        = vec4(0.2, 0.4, 0.8, 1.0); /* Each cube will have the same colour. */vec3  normalized_normals = normalize(normal);vec3  light_direction    = normalize(vec3(light_properties.position - vertex.xyz));float attenuation        = 1.0 / (light_properties.constant_attenuation + (light_properties.linear_attenuation + light_properties.quadratic_attenauation));vec3  camera_direction   = camera_position - vec3(vertex);float diffuse            = max(0.0, dot(normalized_normals, light_direction));vec3  half_vector        = normalize(light_direction + camera_direction);float specular           = 0.0;if (abs(diffuse - 0.0) > EPSILON){specular = max(0.0, dot(half_vector, normal));specular = pow(specular, light_properties.shininess) * light_properties.strength;}vec3 scattered_light  = light_properties.ambient * attenuation + diffuse * attenuation * light_properties.color;vec3 reflected_light  = light_properties.color   * specular              * attenuation;vec3 calculated_color = min(vertex_color.xyz     * scattered_light       + reflected_light, vec3(1.0) );/* Set output variables. */vec4 color_to_be_returned = vec4(calculated_color, 1.0);normal_scene_color  = vec4(0.0);bloom_element_color = vec4(0.0);/* If we are dealing with a cube placed on a diagonal, use white colour.* Otherwise, we want to output a regular cube (which means the previously* calculated cube colour with lighting applied). */if (is_cube_placed_on_diagonal == 1){bloom_element_color = vec4(1.0, 1.0, 1.0, 1.0);}else{normal_scene_color = color_to_be_returned;}
}
复制代码

在片段着色器中,实现了一些用于应用光照效果的颜色计算。

Rendering Scene to Texture

应用程序中,我们多次使用渲染到纹理机制,但让我们仅使用一个案例来解释这一点。

如果我们希望将场景渲染为纹理,我们应该执行以下操作:

生成帧缓冲区和纹理对象。 不要忘记为这些对象设置适当的参数。 我们希望只有一个来自着色器对象的输出(一个带有蓝色和白色立方体的颜色场景)。 我们也需要深度纹理,因为场景被定向光照亮,深度值将用于光计算。

/* Generate objects. */
GL_CHECK(glGenFramebuffers(1,framebufferObjectIdPtr) );
GL_CHECK(glGenTextures    (1,originalTextureObjectIdPtr) );
GL_CHECK(glGenTextures    (1,depthToIdPtr) );
/* Bind generated framebuffer and texture objects to specific binding points. */
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER,*framebufferObjectIdPtr) );
GL_CHECK(glBindTexture  (GL_TEXTURE_2D,*originalTextureObjectIdPtr) );
GL_CHECK(glTexImage2D   (GL_TEXTURE_2D,0,GL_RGBA8,windowWidth,windowHeight,0,GL_RGBA,GL_UNSIGNED_BYTE,NULL) );
GL_CHECK(glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE) );
GL_CHECK(glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE) );
GL_CHECK(glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR) );
GL_CHECK(glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR) );
GL_CHECK(glBindTexture  (GL_TEXTURE_2D,*depthToIdPtr) );
GL_CHECK(glTexImage2D   (GL_TEXTURE_2D,0,GL_DEPTH_COMPONENT32F,windowWidth,windowHeight,0,GL_DEPTH_COMPONENT,GL_FLOAT,NULL) );
GL_CHECK(glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST) );
GL_CHECK(glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST) );
GL_CHECK(glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE) );
GL_CHECK(glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE) );
复制代码

下一步是将生成的纹理对象附加到特定绑定点处的帧缓冲对象。

/* Bind colour and depth textures to framebuffer object. */
GL_CHECK(glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,*originalTextureObjectIdPtr,0) );
GL_CHECK(glFramebufferTexture2D(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_TEXTURE_2D,*depthToIdPtr,0) );
复制代码

剩下的只有一件事:实际渲染。

渲染到纹理对象是通过将framebuffer对象绑定到GL_DRAW_FRAMEBUFFER帧缓冲区绑定点(使用glBindFramebuffer()函数)来实现的。 只有在绑定了默认帧缓冲对象(ID = 0)时,才能渲染到后缓冲区(输出将在屏幕上可见)。

/* Render scene.
*  The scene is rendered to two render targets:
*  - 1. First texture will store color data;
*  - 2. Second texture will store color data, but only for the cubes that should be
*       affected by the bloom operation (remaining objects will not be rendered).
*/
GL_CHECK(glUseProgram(sceneRenderingProgramShaderObjects.programObjectId) );
{/* Bind a framebuffer object to the GL_DRAW_FRAMEBUFFER framebuffer binding point,* so that everything we render will end up in the FBO's attachments. */GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER,sceneRenderingObjects.framebufferObjectId) );/* Set the viewport for the whole screen size. */GL_CHECK(glViewport(0,0,windowWidth,windowHeight) );/* Clear the framebuffer's content. */GL_CHECK(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) );/* [Instanced drawing] *//* Draw scene. */GL_CHECK(glDrawArraysInstanced(GL_TRIANGLES,0,nOfCubeCoordinates,NUMBER_OF_CUBES) );/* [Instanced drawing] */
}
复制代码

Rendering Texture on Screen

本节介绍如何将纹理渲染到屏幕中。 在此示例中,我们使用更高级的纹理渲染,但让我们从初级开始描述问题。

需要做的第一件事是创建一个附加了着色器的程序对象,然后将其链接和使用。

还需要的是一个填充数据的纹理对象(例如,可以通过将场景渲染到纹理中描述的步骤来获取此对象)。 假设textureID指的是纹理对象,textureUnit指的是要用作绑定点的纹理单元。 此纹理必须在特定绑定点绑定到GL_TEXTURE_2D目标。

glActiveTexture(GL_TEXTURE0 + textureUnit); /* textureUnit has to be a value from a range [0, GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS - 1] */
glBindTexture(GL_TEXTURE_2D, textureID);
复制代码

如果要使用默认纹理单元部件,则可以跳过选择活动纹理单元部件。

顶点着色器源:

#version 300 es
precision mediump float;
/* GL_TRIANGLE_FAN-type quad vertex data. */
const vec4 vertex_positions[4] = vec4[4](vec4( 1.0, -1.0, 0.0, 1.0),vec4(-1.0, -1.0, 0.0, 1.0),vec4(-1.0,  1.0, 0.0, 1.0),vec4( 1.0,  1.0, 0.0, 1.0) );
/* Texture UVs. */
const vec2 texture_uv[4]       = vec2[4](vec2(1.0, 0.0),vec2(0.0, 0.0),vec2(0.0, 1.0),vec2(1.0, 1.0) );
/* OUTPUTS */
out vec2 texture_coordinates;
void main()
{/* Return vertex coordinates. */gl_Position         = vertex_positions[gl_VertexID];/* Pass texture coordinated to fragment shader. */texture_coordinates = texture_uv[gl_VertexID];
}
复制代码

如所见,我们使用的是GL_TRIANGLE_FAN类型的四倍顶点数据。 这很重要,因为我们必须使用OpenGL ES绘制功能的相应模式。

片段着色器源:

#version 300 es
precision mediump float;
/* UNIFORMS */
uniform sampler2D sample_texture;
/* INPUTS */
in vec2 texture_coordinates;
/* OUTPUTS */
out vec4 color;
void main()
{/* Return colour. */color = texture(sample_texture, texture_coordinates);
}
复制代码

假设programID是创建并链接的程序对象ID(附加了着色器对象,如上所述)。

/* Set active program. */
glUseProgram(programID);
/* Get texture sample uniform location. */
GLint sampleTextureUniformLocation = glGetUniformLocation(programID, "sample_texture");
/* Set uniform value. */
glUniform1i(sampleTextureUniformLocation, textureUnit);
/* Bind default framebuffer object. */
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
/* Draw texture on a screen. */
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
复制代码

这对于使用默认帧缓冲对象很重要(由于渲染结果将显示在屏幕上)。 这就是我们调用glBindFramebuffer()函数的原因,但默认情况下这不是强制性的。 如果不使用任何帧缓冲对象,则可以跳过此行。

如果使用GL_TEXTURE0,可以跳过设置统一值的行,因为默认情况下会出现这种情况。 如果想使用任何其他绑定点,那么这条线是必不可少的,因为它指出应该渲染哪个纹理对象。

Prepare Scene That Will Be Bloomed

已经描述了如何将场景渲染为纹理。 现在,我们应该有一个包含颜色场景数据的纹理,在我们的例子中是5x5阵列,由蓝色和白色立方体组成。

我们此时想要实现的是从该图像中选择我们想要绽放的所有片段。 我们只想绽放更明亮的立方体。 我们正在使用程序对象来帮助我们选择这些元素。

如果我们使用我们的颜色场景纹理作为此着色器对象的输入,我们可以对其进行采样并检查每个片段的亮度。 如果它高于特定值,则将存储原始片段颜色,否则我们将存储黑色值。 存储这些结果的纹理对象然后可以用作模糊算法的输入。

片段着色器源

static const char getLuminanceImageFragmentShaderSource[] = "#version 300 es\n""precision highp float;\n""/* UNIFORMS */\n""uniform sampler2D texture_sampler;\n""/* INPUTS */\n""in vec2 texture_coordinates;\n""/* OUTPUTS */\n""out vec4 scene_color;\n""#define MIN_LUMINANCE (0.9)\n""void main()\n""{\n""    vec4  sample_color = texture(texture_sampler, texture_coordinates);\n""    float luminance    = 0.2125 * sample_color.x +\n""                         0.7154 * sample_color.y +\n""                         0.0721 * sample_color.z;\n""    if (luminance > MIN_LUMINANCE)\n""    {\n""        scene_color = sample_color;\n""    }\n""    else\n""    {\n""        scene_color =  vec4(0.0);\n""    }\n""}";复制代码

顶点着色器源与Rendering Texture on Screen中描述的相同。

我们希望随后绽放的纹理变得更小(因此bloom算法会更有效)。 这就是为什么我们需要准备dowscaled纹理存储和更新视口。

static void generateDownscaledObjects(GLuint* fboIdPtr,GLuint* toIdPtr)
{ASSERT(fboIdPtr != NULL);ASSERT(toIdPtr  != NULL);/* Generate objects. */GL_CHECK(glGenFramebuffers(1,fboIdPtr) );GL_CHECK(glGenTextures    (1,toIdPtr) );/* Set texture parameters. */GL_CHECK(glBindTexture  (GL_TEXTURE_2D,*toIdPtr) );GL_CHECK(glTexStorage2D (GL_TEXTURE_2D,1,GL_RGBA8,windowWidth   / WINDOW_RESOLUTION_DIVISOR,windowHeight / WINDOW_RESOLUTION_DIVISOR));GL_CHECK(glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE) );GL_CHECK(glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE) );GL_CHECK(glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR) );GL_CHECK(glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR) );/* Make framebuffer object active and bind texture object to it. */GL_CHECK(glBindFramebuffer     (GL_FRAMEBUFFER,*fboIdPtr) );GL_CHECK(glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,*toIdPtr,0) );/* Restore default bindings. */GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER,0) );GL_CHECK(glBindTexture    (GL_TEXTURE_2D,0) );
}
复制代码
/* Get the luminance image, store it in the downscaled texture. */
GL_CHECK(glUseProgram(getLuminanceImageProgramShaderObjects.programObjectId));
{GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER,getLuminanceImageBloomObjects.framebufferObjectId));/* Set the viewport for the whole screen size. */GL_CHECK(glViewport(0,0,windowWidth  / WINDOW_RESOLUTION_DIVISOR,windowHeight / WINDOW_RESOLUTION_DIVISOR) );/* Clear the framebuffer's content. */GL_CHECK(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) );/* Draw texture. */GL_CHECK(glDrawArrays(GL_TRIANGLE_FAN, 0, 4) );
}
复制代码

彩色场景着色器输出

应该绽放的元素

具有白色立方体的缩小纹理将用作下一操作的输入。

Blurring

模糊效果分两个主要步骤应用:

  • 水平模糊
  • 垂直模糊

模糊着色器(水平模糊)的片段着色器源:

static const char blurHorizontalFragmentShaderSource[] = "#version 300 es\n""precision mediump float;\n""/** Defines gaussian weights. */\n""const float gaussian_weights[] = float[] (0.2270270270,\n""                                          0.3162162162,\n""                                          0.0702702703);\n""/* UNIFORMS */\n""/** Radius of a blur effect to be applied. */\n""uniform float     blur_radius;\n""/** Texture sampler on which the effect will be applied. */\n""uniform sampler2D texture_sampler;\n""/* INPUTS */\n""/** Texture coordinates. */\n""in vec2 texture_coordinates;\n""/* OUTPUTS */\n""/** Fragment colour that will be returned. */\n""out vec4 output_color;\n""void main()\n""{\n""    vec4  total_color      = vec4(0.0);\n""    float image_resolution = float((textureSize(texture_sampler, 0)).x);\n""    float blur_step        = blur_radius / image_resolution;\n""    /* Calculate blurred colour. */\n""    /* Blur a texel on the right. */\n""    total_color = texture(texture_sampler, vec2(texture_coordinates.x + 1.0 * blur_step, texture_coordinates.y)) * gaussian_weights[0] +\n""                  texture(texture_sampler, vec2(texture_coordinates.x + 2.0 * blur_step, texture_coordinates.y)) * gaussian_weights[1] +\n""                  texture(texture_sampler, vec2(texture_coordinates.x + 3.0 * blur_step, texture_coordinates.y)) * gaussian_weights[2];\n""    /* Blur a texel on the left. */\n""    total_color += texture(texture_sampler, vec2(texture_coordinates.x - 1.0 * blur_step, texture_coordinates.y)) * gaussian_weights[0] +\n""                   texture(texture_sampler, vec2(texture_coordinates.x - 2.0 * blur_step, texture_coordinates.y)) * gaussian_weights[1] +\n""                   texture(texture_sampler, vec2(texture_coordinates.x - 3.0 * blur_step, texture_coordinates.y)) * gaussian_weights[2];\n""    /* Set the output colour. */\n""    output_color = vec4(total_color.xyz, 1.0);\n""}";
复制代码

模糊着色器(垂直模糊)的片段着色器源:

static const char blurVerticalFragmentShaderSource[] =  "#version 300 es\n""precision mediump float;\n""/** Defines gaussian weights. */\n""const float gaussian_weights[] = float[] (0.2270270270,\n""                                          0.3162162162,\n""                                          0.0702702703);\n""/* UNIFORMS */\n""/** Radius of a blur effect to be applied. */\n""uniform float     blur_radius;\n""/** Texture sampler on which the effect will be applied. */\n""uniform sampler2D texture_sampler;\n""/* INPUTS */\n""/** Texture coordinates. */\n""in vec2 texture_coordinates;\n""/* OUTPUTS */\n""/** Fragment colour that will be returned. */\n""out vec4 output_color;\n""void main()\n""{\n""    vec4  total_color      = vec4(0.0);\n""    float image_resolution = float((textureSize(texture_sampler, 0)).y);\n""    float blur_step        = blur_radius / image_resolution;\n""    /* Calculate blurred colour. */\n""    /* Blur a texel to the top. */\n""    total_color = texture(texture_sampler, vec2(texture_coordinates.x, texture_coordinates.y + 1.0 * blur_step)) * gaussian_weights[0] +\n""                  texture(texture_sampler, vec2(texture_coordinates.x, texture_coordinates.y + 2.0 * blur_step)) * gaussian_weights[1] +\n""                  texture(texture_sampler, vec2(texture_coordinates.x, texture_coordinates.y + 3.0 * blur_step)) * gaussian_weights[2];\n""    /* Blur a texel to the bottom. */\n""    total_color += texture(texture_sampler, vec2(texture_coordinates.x, texture_coordinates.y - 1.0 * blur_step)) * gaussian_weights[0] +\n""                   texture(texture_sampler, vec2(texture_coordinates.x, texture_coordinates.y - 2.0 * blur_step)) * gaussian_weights[1] +\n""                   texture(texture_sampler, vec2(texture_coordinates.x, texture_coordinates.y - 3.0 * blur_step)) * gaussian_weights[2];\n""    /* Set the output colour. */\n""    output_color = vec4(total_color.xyz, 1.0);\n""}";
复制代码

应用模糊效果背后的主要思想是,对于每个纹素,其颜色在每一侧(左/底和右/上)以特定权重展开(根据高斯权重代表统计中的正态分布)。 这是通过进一步从内核(当前模糊的纹理元素)对其具有的片段颜色的较低影响来完成的。

在第一遍中,我们应用了水平模糊。 一旦准备就绪,我们也会使用结果来应用垂直模糊。 该过程显示在架构上。

模糊架构

模糊效果已准备就绪,但结果相当薄弱,与概述中显示的第一个屏幕相当。 为了使其更强,我们多次应用模糊效果(在渲染过程中模糊操作的数量正在改变)。 在下面给出的代码中,可以看到,总模糊效果存储在两个纹理对象中(最后和最后的结果,但是一个迭代存储在两个不同的纹理中)。 这种方法的原因将在Blending中解释。

这里重要的是水平模糊的源纹理正在改变。 在第一个操作中,我们使用缩小的纹理,元素应该是绽放的(Prepare Scene That Will Be Bloomed中描述的结果)。 第(n)次操作的源纹理是第(n-1)次操作的模糊结果。 由于这一点,已经模糊的纹理再次模糊,因此总效果更强。

/* Apply the blur effect.
* The blur effect is applied in two basic steps (note that lower resolution textures are used).
*   a. First, we blur the downscaled bloom texture horizontally.
*   b. The result of horizontal blurring is then used for vertical blurring.
*      The result texture contains image blrured in both directions.
*   c. To amplify the blur effect, steps (a) and (b) are applied multiple times
*      (with an exception that we now use the resulting blurred texture from the previous pass
*       as an input to the horizontal blurring pass).
*   d. The result of last iteration of applying the total blur effect (which is the result after the vertical blur is applied)
*      is stored in a separate texture. Thanks to that, we have the last and previous blur result textures,
*      both of which will be then used for continuous sampling (for the blending pass).
*
*/
/* Bind a framebuffer object to the GL_DRAW_FRAMEBUFFER framebuffer binding point,
* so that everything we render will end up in the FBO's attachments. */
GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER,blurringObjects.framebufferObjectId) );
/* Set the lower viewport resolution. It corresponds to size of the texture we will be rendering to. */
GL_CHECK(glViewport(0,0,windowWidth  / WINDOW_RESOLUTION_DIVISOR,windowHeight / WINDOW_RESOLUTION_DIVISOR) );
GL_CHECK(glEnable  (GL_SCISSOR_TEST) );
/* Apply the blur effect multiple times. */
for (int blurIterationIndex = 0;blurIterationIndex < currentNumberOfIterations;blurIterationIndex++)
{/* FIRST PASS - HORIZONTAL BLUR* Take the texture showing cubes which should be bloomed and apply a horizontal blur operation.*/GL_CHECK(glUseProgram(blurringHorizontalProgramShaderObjects.programObjectId) );{/* Attach the texture we want the color data to be rendered to the current draw framebuffer.*/GL_CHECK(glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,blurringObjects.textureObjectIdHorizontal,0) );/* In first iteration we have to take the texture which shows the cubes we want blurred.* Later, we have to take the same texture that has already been blurred vertically. */if (blurIterationIndex == 0){GL_CHECK(glUniform1i(blurringHorizontalProgramLocations.uniformTextureSampler,TEXTURE_UNIT_BLOOM_SOURCE_TEXTURE) );}else{GL_CHECK(glUniform1i(blurringHorizontalProgramLocations.uniformTextureSampler,TEXTURE_UNIT_BLURRED_TEXTURE) );}/* Draw texture. */GL_CHECK(glDrawArrays(GL_TRIANGLE_FAN, 0, 4) );} /* FIRST PASS - HORIZONTAL BLUR *//* SECOND PASS - VERTICAL BLUR* Take the result of the previous pass (horizontal blur) and apply a vertical blur to this texture.*/GL_CHECK(glUseProgram(blurringVerticalProgramShaderObjects.programObjectId) );{if (blurIterationIndex == currentNumberOfIterations - 1){/* In case of the last iteration, use a different framebuffer object.* The rendering results will be written to the only color attachment of the fbo. */GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER,strongerBlurObjects.framebufferObjectId) );}else{/* Bind a texture object we want the result data to be stored in.*/GL_CHECK(glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,blurringObjects.textureObjectIdVertical,0) );}/* Set uniform values. */GL_CHECK(glUniform1i(blurringVerticalProgramLocations.uniformTextureSampler,TEXTURE_UNIT_HORIZONTAL_BLUR_TEXTURE) ); /* Indicates which texture object content should be blurred. *//* Draw texture. */GL_CHECK(glDrawArrays(GL_TRIANGLE_FAN, 0, 4) );} /* SECOND PASS - VERTICAL BLUR */
} /* for (int blur_iteration_index = 0; i < numberOfIterations; blur_iteration_index++) */
GL_CHECK(glDisable(GL_SCISSOR_TEST));
复制代码

请查看下面的架构,看看我们在此操作后获得的效果。

模糊操作的效果。 较强的模糊效果对应于较高数量的模糊迭代。

Blending

由于之前的步骤,我们有:

  • 包含颜色场景的纹理对象 - 颜色场景(Rendering Scene to Texture)
  • 应用较弱模糊效果的纹理对象持有绽放对象(Blurring中描述的第n-1次迭代的结果)
  • 应用具有更强模糊效果的绽放对象的纹理对象(Blurring中描述的第n次迭代的结果)

我们希望在此步骤中实现的是混合所有这些纹理并显示结果。

我们的方法是模糊效果强度的平滑变化。 这就是为什么我们需要来自模糊循环的两个结果(第n和第n - 1)。 我们将使用OpenGL ES着色语言mix()函数来获得此效果。 这种方法不是强制性的。 你可以只使用最后一个模糊循环的结果(然后不需要着色器中的mix()操作),但是它会导致模糊效果强度变化的锯齿状结果,这看起来不如平滑变化那么好。

首先,请查看下面的代码。

/* Apply blend effect.* Take the original scene texture and blend it with texture that contains the total blurring effect.*/
GL_CHECK(glUseProgram(blendingProgramShaderObjects.programObjectId) );
{/* Bind the default framebuffer object. That indicates that the result is to be drawn to the back buffer. */GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0) );/* Set viewport values so that the rendering will take whole screen space. */GL_CHECK(glViewport(0, 0, windowWidth, windowHeight) );/* Set uniform value. */GL_CHECK(glUniform1f(blendingProgramLocations.uniformMixFactor, mixFactor) ); /* Current mixFactor will be used for mixing two textures color values* (texture with higher and lower blur effect value). *//* Clear framebuffer content. */GL_CHECK(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) );/* Draw texture. */GL_CHECK(glDrawArrays(GL_TRIANGLE_FAN, 0, 4) );
}
复制代码

如所见,我们使用默认的帧缓冲对象,因此效果将显示在屏幕上而不是存储在渲染目标中。 唯一改变的是混合因素。 这将用作着色器中mix()函数的参数。 取决于模糊效果方向(如果它增加或减少),mixFactor值会相应地更改(从0到1)。

/* Mix factor value is calculated for a specific frame (for a specific time).* - The number of blur passes varies from MIN_NUMBER_OF_BLUR_PASSES to MAX_NUMBER_OF_BLUR_PASSES*   and to MIN_NUMBER_OF_BLUR_PASSES again which indicates the constant animation of increasing*   and decreasing blur effect strength.* - For each frame (time) there is a check done to verify the current number of blur passes.* - Once we get the current number of blur passes, we have to calculate the mix factor:*   It is changing from 0 to 1 (if the blur effect is increasing) or from 1 to 0 (if the blur effect is decreasing).*   This value is set based on a time which passed from the beginning of current number of blur passes rendering in*   compare to the total time requested for changing this number.**   The 'rendering frame for a specific time' approach is used to avoid a situation of a different effect for slower and faster devices.*/
/* Increase or decrease mixFactor value (depends on blurEffectDirection). */
timeIntervalIndex = (int)(time / TIME_INTERVAL);
nOfIterations     = (int) timeIntervalIndex % numberOfBlurPasses;
if (nOfIterations >= (numberOfBlurPasses / 2))
{nOfIterations       = numberOfBlurPasses - (nOfIterations % numberOfBlurPasses) - 1;blurEffectDirection = BLUR_EFFECT_DECREASE;
}
mixFactor                 = (time - ((int)(time / TIME_INTERVAL) * TIME_INTERVAL)) / TIME_INTERVAL;
currentNumberOfIterations = MIN_NUMBER_OF_BLUR_PASSES + nOfIterations;
if (blurEffectDirection == BLUR_EFFECT_DECREASE)
{mixFactor = 1.0f - mixFactor;
}
if (currentNumberOfIterations != lastNumberOfIterations)
{shouldSceneBeUpdated = true;
}
/* Store current number of iterations for future use. */
lastNumberOfIterations = currentNumberOfIterations;
复制代码

在片段着色器对象(下面给出的代码)中,有三个纹理源输入(相应于上面列出的那些)。 混合较弱且较强的模糊纹理(应用mix()函数返回线性混合)。 将返回的总颜色只是模糊纹理和原始场景纹理的线性混合的总和。

#version 300 es
precision mediump float;
/* UNIFORMS */
uniform float     mix_factor;
uniform sampler2D original_texture;
uniform sampler2D stronger_blur_texture;
uniform sampler2D weaker_blur_texture;
/* INPUTS */
in vec2 texture_coordinates;
/* OUTPUTS */
out vec4 color;
void main()
{vec4 stronger_blur_texture_color = texture(stronger_blur_texture, texture_coordinates);vec4 weaker_blur_texture_color   = texture(weaker_blur_texture,   texture_coordinates);vec4 mixed_blur_color            = mix(weaker_blur_texture_color, stronger_blur_texture_color, mix_factor);vec4 original_color              = texture(original_texture, texture_coordinates);/* Return blended colour. */color = original_color + mixed_blur_color;
}
复制代码

Min Max Blending

该应用程序演示了在OpenGL ES 3.0中以GL_MIN和GL_MAX模式进行混合的行为。

呈现人体头部磁共振的3D纹理

该应用程序演示了在GL_MIN和GL_MAX模式下混合的行为。 它呈现3D纹理,其由从人头部的磁共振获得的一系列灰度图像组成。 图像在Z轴上一个接一个地放置,因此当启用混合时,它们模仿头部的3D模型。

然后旋转纹理坐标,以便观察者可以从不同的角度看模型,每5秒后,混合方程就会改变。 由于U / V / W坐标取自间隔<0.0,1.0>并且它们被夹紧到边缘,因此可能会出现特定旋转角度的一些失真。 这就是为什么应用程序在原始图像的后面和前面添加了一些空白层。 现在,如果旋转坐标超过间隔,则仅重复附加边缘层,从而创建无噪声背景。

由于图像包含大量黑色,因此定期最小混合会导致屏幕上出现黑色方块。 因此,在片段着色器中应用阈值,该阈值防止渲染不够亮的片段。 此外,对于这两种类型的混合,必须修改输出亮度的对比度以查看更多细节。

要使用自己的输入图像,请检查其格式并调整最小混合阈值,附加边缘图层的亮度和对比度修改器的值。

Program Object

在应用程序中,只使用了一个程序对象。 它负责在屏幕上渲染3D纹理,旋转它并丢弃在使用最小混合选项时不够亮的碎片。 生成和使用程序对象,将着色器附加到程序对象并编译它们。

顶点着色器代码

/* Input vertex position. */
in vec4 inputPosition;
/* Input U/V/W texture coordinates. */
in vec3 inputUVWCoordinates;
/* Constant transformation matrices. */
uniform mat4 cameraMatrix;
uniform mat4 projectionMatrix;
/* Vector storing rotation coefficients for rotation matrices. */
uniform vec3 rotationVector;
/* Number of instances that are going to be drawn. */
uniform int instancesCount;
/* Output texture coordinates passed to fragment shader. */
out vec3 uvwCoordinates;
void main()
{mat4 modelViewProjectionMatrix;/* Matrix rotating texture coordinates around X axis. */mat3 xRotationMatrix = mat3(1.0,  0.0,                            0.0,0.0,  cos(radians(rotationVector.x)), sin(radians(rotationVector.x)), 0.0, -sin(radians(rotationVector.x)), cos(radians(rotationVector.x)));/* Matrix rotating texture coordinates around Y axis. */mat3 yRotationMatrix = mat3(cos(radians(rotationVector.y)), 0.0, -sin(radians(rotationVector.y)), 0.0,                            1.0,  0.0,sin(radians(rotationVector.y)), 0.0,  cos(radians(rotationVector.y)));/* Matrix rotating texture coordinates around Z axis. */mat3 zRotationMatrix = mat3( cos(radians(rotationVector.z)), sin(radians(rotationVector.z)), 0.0, -sin(radians(rotationVector.z)), cos(radians(rotationVector.z)), 0.0, 0.0,                             0.0,                            1.0);/* U/V/W coordinates pointing at appropriate layer depending on gl_InstanceID. */vec3 translatedUVWCoordinates = inputUVWCoordinates - vec3(0.0, 0.0, float(gl_InstanceID) / float(instancesCount - 1));/* * Translate from <0.0, 1.0> interval to <-1.0, 1.0> to rotate texture coordinates around their center. * Otherwise, the rotation would take place around the XYZ axes which would spoil the effect.* The translated coordinates should be translated back to <0.0, 1.0> after all rotations are done.*/translatedUVWCoordinates = translatedUVWCoordinates * 2.0 - vec3(1.0);/* Rotate texture coordinates. */translatedUVWCoordinates = xRotationMatrix * translatedUVWCoordinates;translatedUVWCoordinates = yRotationMatrix * translatedUVWCoordinates;translatedUVWCoordinates = zRotationMatrix * translatedUVWCoordinates;/* Translate back to <0.0, 1.0> interval. */uvwCoordinates = (translatedUVWCoordinates + vec3(1.0)) / 2.0;/* Calculate Model-View-Projection matrix. */modelViewProjectionMatrix = projectionMatrix * cameraMatrix;/* Calculate position of vertex. With each instance squares are drawn closer to the viewer. */gl_Position = modelViewProjectionMatrix * (inputPosition + vec4(0.0, 0.0, float(gl_InstanceID) / float(instancesCount - 1), 0.0));
}
复制代码

顶点着色器负责旋转纹理,在我们的示例中,这意味着基于旋转矢量值更新3D纹理的特定图层的UVW坐标。

片段着色器代码

precision mediump float;
precision mediump int;
precision mediump isampler3D;
/* Value used to normalize colour values. */
const highp int maxShort = 32768;
/* Value used to brighten output colour. */
const float contrastModifier = 3.0;/* Input taken from vertex shader. */
in vec3 uvwCoordinates;
/* 3D integer texture sampler. */
uniform isampler3D textureSampler;
/* Boolean value indicating current blending equation. */
uniform bool isMinBlending;
/* Threshold used for min blending. */
uniform float minBlendingThreshold;
/* Output variable. */
out vec4 fragColor;
void main()
{/* Loaded texture short integer data are in big endian order. Swap the bytes. */ivec4 initialTexture          = ivec4(texture(textureSampler, uvwCoordinates).rrr, 1.0);ivec4 swappedBytesTextureTemp =  (initialTexture << 8) & ivec4(0xFF00);ivec4 swappedBytesTexture     = ((initialTexture >> 8) & ivec4(0x00FF)) | swappedBytesTextureTemp;/* Determine output fragment colour. */fragColor = vec4(swappedBytesTexture) / float(maxShort) * contrastModifier;/* If min blending is set, discard fragments that are not bright enough. */if (isMinBlending && length(fragColor) < minBlendingThreshold){discard;}
}
复制代码

片段着色器负责对3D纹理进行采样,并且在使用最小混合模式时,丢弃不够亮的片段。

3D Textures

三维纹理由一堆2D纹理表示。 要在OpenGL ES 3.0中创建3D纹理,需要:

  1. 创建纹理对象ID;
GL_CHECK(glGenTextures(1, &textureID));
复制代码
  1. 将它绑定到GL_TEXTURE_3D目标;
GL_CHECK(glBindTexture(GL_TEXTURE_3D, textureID));
复制代码
  1. 初始化纹理存储;
/* Initialize storage space for texture data. */
GL_CHECK(glTexStorage3D(GL_TEXTURE_3D,1,GL_R16I,textureWidth,textureHeight,textureDepth));
复制代码
  1. 设置纹理对象参数;
/* Set texture parameters. */
GL_CHECK(glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S,     GL_CLAMP_TO_EDGE));
GL_CHECK(glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T,     GL_CLAMP_TO_EDGE));
GL_CHECK(glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R,     GL_CLAMP_TO_EDGE));
GL_CHECK(glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
GL_CHECK(glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
复制代码
  1. 数据填充每个纹理级别(应为每个纹理层调用上述函数)。
/* Set 2D image at the current textureZOffset. */
GL_CHECK(glTexSubImage3D(GL_TEXTURE_3D,0,0,0,textureZOffset,textureWidth,textureHeight,1,GL_RED_INTEGER,GL_SHORT,textureData));
复制代码

一旦完成上述所有步骤,我们就可以使用纹理对象作为程序对象3D uniform 采样器的输入。 我们只使用一个采样器对象和一个默认纹理单元,因此下面描述的步骤不是必需的,但无论如何我们都会发布它们,只是为了展示机制。

首先,我们正在查询3D采样器的位置。

GLint textureSamplerLocation = GL_CHECK(glGetUniformLocation(programID, "textureSampler"));
复制代码

请注意,第二个参数应对应着色器中使用的 uniform 名称。

下一步是检查检索到的位置是否有效。 如果返回值为-1,则统一被视为非活动状态,并且将忽略任何设置 uniform 值的尝试。

ASSERT(textureSamplerLocation != -1, "Could not find location for uniform: textureSampler");
复制代码

一旦我们确定,已找到 uniform,我们就可以设定它的价值。 它是通过一些基本步骤实现的。

设置活动纹理单元(默认情况下GL_TEXTURE0处于活动状态,但我们仍然想调用它来显示机制)。

GL_CHECK(glActiveTexture(GL_TEXTURE0));
复制代码

然后,我们需要将纹理对象绑定到GL_TEXTURE_3D目标。

GL_CHECK(glBindTexture(GL_TEXTURE_3D, textureID));
复制代码

现在,通过调用下面显示的函数,使用textureID命名的纹理对象将用作程序对象的输入。

GL_CHECK(glUniform1i(textureSamplerLocation, 0));
复制代码

在glUniform1i()调用中,第二个参数对应于GL_TEXTURE0纹理单元中的0。 如果我们想在GL_TEXTURE1纹理单元使用绑定到GL_TEXTURE_3D目标的3D纹理对象,则glUniform1i()调用中的第二个参数应该等于1。

要在屏幕上绘制3D纹理,我们使用实例绘制技术分别渲染每个纹理图层。

/* Draw a single square layer consisting of 6 vertices for textureDepth times. */
GL_CHECK(glDrawArraysInstanced(GL_TRIANGLES, 0, 6, textureDepth));
复制代码

Blending

该应用程序的主要思想是显示GL_MIN和GL_MAX混合方程之间的差异。 只是为了澄清:特定的混合方程指定了如何确定像素颜色。 更确切地说:如果像素已经确定了颜色(它总是有)并且我们想要为它应用新颜色,那么我们可以指定两种颜色的混合方式:我们可以简单地添加两种颜色, 减去它们或我们可以使用两者的最小值或最大值(这是我们的情况)。

首先,我们需要启用混合。 如果没有它,新颜色将取代旧颜色,不会发出混合。

/* Enable blending. */
GL_CHECK(glEnable(GL_BLEND));
复制代码

然后,我们可以通过调用来改变混合方程

/* Set new blend equation. */
GL_CHECK(glBlendEquation(GL_MIN))
复制代码

或者

/* Set new blend equation. */
GL_CHECK(glBlendEquation(GL_MAX));
复制代码

具体混合方程的结果如下所示。

不同混合方程的结果:GL_MAX(左侧)和GL_MIN(右侧)。

OpenGL ES SDK for Android - 3相关推荐

  1. OpenGL ES SDK for Android - 4

    Integer Logic 该应用程序根据规则30使用OpenGL ES 3.0模拟细胞自动机现象. 它使用两个程序对象,这两个程序以乒乓方式使用两个纹理. 第一个程序将ping纹理(ping)作为输 ...

  2. 关于openGL, openGL ES, openVG及android中2D调用关系的报告

    关于openGL, openGL ES, openVG及android中2D调用关系的报告 http://blog.chinaunix.net/u3/99423/showart_2203591.htm ...

  3. android opengl es 绘制余弦曲线,Android OpenGL ES - 绘制线、面

    前言 之前一篇文章讲了如何绘制点,所谓两点成线,三点成面.一个立体图形就是由很多面组成,在OpenGL ES中,面特指一个三角形. 绘制调用glDrawArrays(int mode, int fir ...

  4. opengl es java_java – 在Android OpenGL ES App中加载纹理

    1)您应该根据需要分配尽可能多的纹理名称.一个用于您正在使用的每个纹理. 加载纹理是一项非常繁重的操作,会使渲染管道停顿.所以,你永远不应该在游戏循环中加载纹理.您应该在呈现纹理的应用程序状态之前具有 ...

  5. A summary of OpenGL ES 3.1 demos and samples

    发贴人 hans-kristian 于2015-4-14 7:11:19在ARM Mali Graphics About me Hi, I am Hans-Kristian Arntzen! This ...

  6. Android中用OpenGL ES Tracer分析绘制过程

    Tracer for OpenGL ES(http://developer.android.com/tools/help/gltracer.html)是Android SDK中新增加的开发工具,可逐帧 ...

  7. 【Android】OpenGL ES for Android 教程1 - HelloWorld

    本教程及后续教程全部参考或者转载于:OpenGL ES Tutorial for Android 本例相当于openGL的HelloWorld程序. 先贴代码,代码中的注释比较具体了. 主Activi ...

  8. 使用Android OpenGL ES 2.0绘图之二:定义形状

    传送门☞Android兵器谱☞转载请注明☞http://blog.csdn.net/leverage_1229 传送门☞系统架构设计☞转载请注明☞http://blog.csdn.net/levera ...

  9. android平台下OpenGL ES 3.0从零开始

    OpenGL ES 3.0学习实践 android平台下OpenGL ES 3.0从零开始 android平台下OpenGL ES 3.0绘制纯色背景 android平台下OpenGL ES 3.0绘 ...

最新文章

  1. 微信小程序修改整体背景颜色
  2. Python基础知识(第六天)
  3. docker 容器 exited_Docker实战006:docker容器使用详解
  4. 程序员35岁前需要完成的10件事
  5. PL/SQL数据类型
  6. centreon与nagios整合
  7. BOOST库介绍(三)——网络编程相关的库
  8. 利用Python和OpenCV进行面部表情识别
  9. YUI 3 Cookbook 中文版
  10. java kindeditor ssh,typecho KindEditor插件
  11. 机器人电焊电流电压怎么调_焊电焊电流怎么调节,调多少合适?
  12. 访问学者博士后面签后的几种情况?
  13. css怎么两线合并,【2人回答】CAD中如何把两条线合并成一条线?-3D溜溜网
  14. 如何解决 Android浏览器查看背景图片模糊的问题?
  15. Ubuntu还需要做什么才能替代Windows
  16. 怎样使用word模板?两分钟教你搞定!
  17. python引用自己写的文件
  18. Java 使用dcm4che的工具类findscu查询pacs数据
  19. # 经典目标检测识别方法概述RCNN/FAST/FASTER RCNN/MASK RCNN/SSD/DSSD/YOLO V1/YOLO V2等
  20. 第19章、 认识与分析登录档

热门文章

  1. DL1 - Neural Networks and Deep Learning
  2. 升级到JUnit5的7个理由
  3. 梯度消亡(Gradient Vanishing)和梯度爆炸(Gradient Exploding)
  4. 系统日报-20220421(Databricks 缘何成功?)
  5. ZBrush - 动物毛发制作及渲染
  6. 我同意 三江方士 对 哥德巴赫猜想 的 看法
  7. 月入万元快递哥遭遇AI小鸟怎么办
  8. 如何保障银行日志安全合规审计
  9. 凑个热闹 谈谈网红沈大师
  10. 如何调用腾讯的IP库?