OpenGL.Shader: implementación de 10 sombras-mapa de bits de profundidad FBO
Siento que no he publicado un artículo por un tiempo, hay muchas razones. La razón principal es que estoy un poco ocupado recientemente, y el contenido del nuevo artículo es demasiado complicado y difícil, y lleva tiempo prepararlo.
En primer lugar, revisemos el FBO. Nuestra práctica anterior simple en OpenGL.ES en Android: 23-Watermark Recording introdujo lo que es FBO. En ese momento, lo usamos para el renderizado fuera de la pantalla, con la ayuda de su espacio de color de búfer personalizado. Resuelve el problema de los conflictos de transparencia. No repetiré la discusión aquí sobre qué es un FBO. Es necesario enfatizar que un FBO es solo un contenedor, que puede montar muchos tipos de objetos de renderizado. Esta vez necesitamos usar otro búfer de profundidad de objeto de renderizado nuevo.
No digas tonterías, ve directamente a FrameBufferObject.hpp
#pragma once
#ifndef FRAME_BUFFER_OBJECT_HPP
#define FRAME_BUFFER_OBJECT_HPP
#define FBO_NONE 0x0000
#define FBO_RGBA 0x0001
#define FBO_DEPTH 0x0002
#include <GLES3/gl3.h>
#include "zzr_common.h"
class FrameBufferObject
{
public:
unsigned int _width;
unsigned int _height;
unsigned int _fboID;
unsigned int _rgbaTexId;
unsigned int _depthTexId;
unsigned int _depthRboId;
unsigned int _type;
public:
FrameBufferObject()
{
_width = 0;
_height = 0;
_fboID = 0;
_rgbaTexId = 0;
_depthTexId = 0;
_depthRboId = 0;
_type = FBO_NONE;
}
int getDepthTexId() {
return _depthTexId;
}
int getRgbaTexId() {
return _rgbaTexId;
}
bool setup(int w, int h, int type)
{
_type = static_cast<unsigned int>(type);
_width = static_cast<unsigned int>(w);
_height = static_cast<unsigned int>(h);
glGenFramebuffers(1, &_fboID);
glBindFramebuffer(GL_FRAMEBUFFER, _fboID);
if(_type == FBO_DEPTH) {
createDepthTexture();
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, _depthTexId, 0);
}
if(_type == FBO_RGBA) {
createRgbaTexture();
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _rgbaTexId, 0);
createDepthRenderBuffer();
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRboId);
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
GLenum err = glGetError();
while(err!=GL_NO_ERROR) {
LOGE("fbo.setup.glTexImage2D : 0x%08x\n", err);
err = glGetError();
}
return true;
}
void begin()
{
GLenum err = glGetError();
while(err!=GL_NO_ERROR) {
LOGE("fbo.begin : 0x%08x\n", err);
err = glGetError();
}
glBindFramebuffer(GL_FRAMEBUFFER, _fboID);
if(_type == FBO_DEPTH) {
glDrawBuffers(0, GL_NONE);
glReadBuffer(GL_NONE);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, _depthTexId, 0);
}
if(_type == FBO_RGBA) {
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _rgbaTexId, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRboId);
}
GLenum fbo_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if(fbo_status!=GL_FRAMEBUFFER_COMPLETE) {
LOGE("glCheckFramebufferStatus check err : 0x%08x\n", fbo_status);
}
}
void end()
{
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
private:
void createDepthTexture()
{
glGenTextures(1, &_depthTexId);
glBindTexture(GL_TEXTURE_2D, _depthTexId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, _width, _height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, 0);
}
void createRgbaTexture()
{
glGenTextures(1, &_rgbaTexId);
glBindTexture(GL_TEXTURE_2D, _rgbaTexId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, _width, _height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
}
void createDepthRenderBuffer()
{
glGenRenderbuffers( 1, &_depthRboId );
glBindRenderbuffer( GL_RENDERBUFFER, _depthRboId );
glRenderbufferStorage( GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, _width, _height );
}
};
#endif // FRAME_BUFFER_OBJECT_HPP
El código básico es el anterior. De acuerdo con la situación común, divido FBO en dos tipos, FBO_DEPTH (modo de textura de profundidad) y FBO_RGBA (modo de textura de color). Literalmente, FBO_RGBA es el tipo de uso introducido antes. La diferencia es que el RenderBuffer del punto de conexión de profundidad (GL_DEPTH_ATTACHMENT) se agrega esta vez. Esto se debe a que esta vez estamos en el modelo 3D y la información de profundidad no se puede ignorar. Después de enviar la salida al FBO, necesitamos un búfer para retener esta información de profundidad. Tal fbo puede retener completamente toda la información que necesitamos.
Para los estudiantes que no entienden, trato de comentar el código relacionado con la profundidad, renderbuffer. El efecto es el siguiente (dibuja primero el cubo y luego el suelo):
Este es el efecto de no tener datos de prueba de profundidad, puede imaginar lo importante que es este búfer de renderizado de profundidad. Aquí hay una breve introducción al objeto de renderizado RenderBuffer. En realidad, es el mismo tipo de cosa que el objeto Texture. Ambos son un objeto contenedor que se usa para guardar la salida en OpenGL. El objeto Texture es más flexible y se puede pasar al sombreador para su uso, pero A veces, la textura no es una panacea. Al igual que en el ejemplo actual, es necesario almacenar la información de color y la información de profundidad. En este momento, Renderbuffer es útil.
Entonces, ¿qué es un mapa de bits de profundidad? Veamos el código anterior. En el mapa de bits de profundidad creado por la función createDepthTexture en modo FBO_DEPTH, la única diferencia con createRgbaTexture son los parámetros de glTexImage2D. No mires estos parámetros. Realmente pisé estos parámetros. Estos pozos no son pozos. Busqué en toda la red, incluido Google, y no obtuve una respuesta clara. Te diré lo que pienso. Si lo entiendes correctamente, deja un mensaje como guía.
void createDepthTexture ()
{ glGenTextures (1, & _depthTexId); glBindTexture (GL_TEXTURE_2D, _depthTexId); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexImage2D (GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, _width, _height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, 0); // LOGE ("createDepthTexture check err 4: 0x% 08x \ n", glGetError ()); }
En OpenGL Wiki ( https://www.khronos.org/opengl/wiki/GLAPI/glTexImage2D ), la descripción del parámetro de glTexImage2D, el tercer parámetro internalFormat se describe de la siguiente manera:
internalFormat
Especifica el número de componentes de color en la textura. Debe ser uno de los formatos internos básicos que se indican en la Tabla 1, uno de los formatos internos de tamaño que se indican en la Tabla 2 o uno de los formatos internos comprimidos que se indican en la Tabla 3, a continuación.
Luego, señale las siguientes tres Tablas y lea las tres tablas con atención. El punto importante es que las Tablas 2 y 3 son en realidad Formatos internos base que apuntan a la Tabla 1. Bajo el corte GL ES del terminal móvil, solo se retiene el GL_DEPTH_COMPONENT final y el resto GL_DEPTH_COMPONENT16 / GL_DEPTH_COMPONENT24 / GL_DEPTH_COMPONENT32F no son aplicables, sin importar cuántas profundidades de bits admita la GPU actual (consulta a través de glGetIntegerv (GL_DEPTH_BITS, & depth_bits)), cuántas profundidades de bits siempre se configuran para el uso de EGLEPTH_ONCOM
Luego, el penúltimo parámetro Tipo es determinar qué usar de acuerdo con la profundidad de bits establecida por EGL, 8 bits = GL_UNSIGNED_BYTE, 16 bits = GL_UNSIGNED_SHORT;
Bueno, después de la configuración correcta de depth_texture, podemos guardar la prueba de profundidad de salida en la textura y enviarla como una textura a la pantalla.
Todos presten atención al mapa de bits de profundidad en la esquina superior izquierda. Al principio, estamos cerca del piso desde la perspectiva. Cuanto más cerca estemos del frente, más cerca del valor de profundidad es 0. Luego vi el contorno del cubo, y el piso detrás del cubo también mostró profundidad. (Mire con atención, el gris es muy claro) Luego suelte otras partes del código para una fácil comprensión.
void ShadowFBORender::renderOnDraw(double elpasedInMilliSec)
{
if (mEglCore == NULL || mWindowSurface == NULL) {
LOGW("Skipping drawFrame after shutdown");
return;
}
mWindowSurface->makeCurrent();
glEnable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
glViewport(0,0, mViewWidth, mViewHeight);
lightCube.render(mCamera3D);
land.render(mCamera3D);
renderDepthFBO();
mWindowSurface->swapBuffers();
}
void ShadowFBORender::renderDepthFBO() {
depthFBO.begin();
{
glEnable(GL_DEPTH_TEST);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
glClearDepthf(1.0f);
lightCube.render(mCamera3D);
land.render(mCamera3D);
}
depthFBO.end();
pip.setTextureId(depthFBO.getDepthTexId()); // 深度纹理
//pip.setTextureId(depthFBO.getRgbaTexId());// 彩色纹理
pip.render();
}
pip-> PIPicture es un tipo de objeto que utiliza proyección ortogonal para dibujar textura en la pantalla, es muy simple y simple. Código de referencia https://github.com/MrZhaozhirong/NativeCppApp -> ShadowFBORender.cpp / FrameBufferObject.hpp / PIPicture.hpp
En el próximo capítulo, discutiremos cómo usar mapas de bits de profundidad para lograr sombras claras.