[Análisis detallado del motor de juego sobrecargado] Principio de selección de objetos del editor con el mouse

     El área de vista de escena de Overload tiene una función de selección del mouse. Después de hacer clic en el objeto seleccionado, se mostrará en el panel Inspector. Este artículo analiza el principio detrás de la función de captura del mouse.

1. Buffer de cuadros OpenGL

Hay dos formas comunes de implementar la selección del mouse: renderizar ID en textura e intersección de proyección de rayos. La sobrecarga utiliza la identificación de representación para la textura, y su implementación requiere el uso del búfer de cuadros de OpenGL, por lo que primero debe comprender el búfer de cuadros de OpenGL.

El caché que generalmente discutimos se refiere al caché de ventana de forma predeterminada, que se muestra directamente en la ventana. También podemos crear un caché personalizado para permitir que la canalización de la GPU se represente en la textura y luego usar esta textura en otro lugar. Y los datos en la textura son solo un valor binario, no necesariamente un color, y se puede escribir cualquier dato significativo.

Si queremos crear un objeto framebuffer, debemos llamar a glGenFramebuffers() y obtener un identificador no utilizado. Cuando utilice el búfer de cuadros, primero debe llamar al enlace glBindFramebuffer (GL_FRAMEBUFFER, bufferID). Si desea renderizar en un mapa de textura, debe llamar a glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENTi, texturaId, nivel) para asociar el nivel del mapa de textura al archivo adjunto del búfer de fotograma. Si el renderizado también requiere caché de profundidad y caché de plantilla, entonces también se necesita un caché de renderizado.

El caché de renderizado también es un área de memoria eficiente administrada por OpenGL, puede almacenar datos en un formato específico y solo tiene sentido si está asociado con un búfer de fotogramas. Llamar a glGenRenderbuffers puede crear un búfer de renderizado y también se requieren operaciones de enlace al operarlo. Utilice glBindRenderbuffer al vincular.

Después de ver esto, ¿crees que el frame buffer es demasiado complicado de usar? De hecho, la configuración del búfer de fotogramas son todos códigos de formato fijo y las rutinas son básicamente las mismas: primero vamos a unirlas con pseudocódigo. Suponiendo que nuestro programa está diseñado para el proceso, primero llamamos a la función init para la inicialización, y luego el bucle principal llama continuamente a la función de visualización para la representación. El pseudocódigo aproximado es el siguiente:

init() {
     glGenFramebuffers(1, &m_bufferID);  // 生成帧缓存
     glGenTextures(1, &m_renderTexture)  // 生成纹理对象
     // 设置纹理格式
     glBindTexture(GL_TEXTURE_2D, m_renderTexture);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
     glBindTexture(GL_TEXTURE_2D, 0);
     // 将纹理作为颜色附件绑定到帧缓存上
     glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_renderTexture, 0);

     glGenRenderbuffers(1, &m_depthStencilBuffer); // 生成渲染对象
     // 设置渲染对象数据格式
	 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_STENCIL, p_width, p_height);
     // 配置成帧缓存的深度缓冲与模板缓冲附件
     glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthStencilBuffer);
	 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_depthStencilBuffer);
  }

display() {
    // 1. 绑定帧缓存
    glBindFramebuffer(GL_FRAMEBUFFER, m_bufferID);

    // 2. 渲染物体到帧缓存
    glClearColor();
    glClear();
    draw();

    // 3. 解绑帧缓存
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    // 4. 使用帧缓存渲染出来的纹理
    ...
    glActiveTexture();
    glBindTexture(GL_TEXTURE_2D, id);
    
}

  El código de la función init sigue siendo esencialmente el mismo.                                       

2. Encapsulación de FrameBuffer por sobrecarga

La sobrecarga encapsula FrameBuffer en una clase Framebuffer y el código se encuentra en Framebuffer.h y Framebuffer.cpp. Primero mire el archivo Framebuffer.h. La clase Framebuffer se define de la siguiente manera. Si no está familiarizado con los términos en los comentarios, debe aprender OpenGL.

class Framebuffer
	{
	public:
		/**
		* 构造函数,会直接创建一个帧缓冲
		* @param p_width 帧缓冲的宽
		* @param p_height 帧缓存的高
		*/
		Framebuffer(uint16_t p_width = 0, uint16_t p_height = 0);

		/**
		* 析构函数
		*/
		~Framebuffer();

		/**
		* 绑定帧缓冲,对其进行操作
		*/
		void Bind();

		/**
		* 解除绑定
		*/
		void Unbind();

		/**
		* 对帧缓冲的大小进行改变
		* @param p_width 新的帧缓冲宽度
		* @param p_height 新的帧缓冲高度
		*/
		void Resize(uint16_t p_width, uint16_t p_height);

		/**
		* 帧缓冲的id
		*/
		uint32_t GetID();

		/**
		* 返回OpenGL纹理附件的id
		*/
		uint32_t GetTextureID();

		/**
		* 返回渲染缓存的id,这个方法在Overload中其他地方没有使用
		*/
		uint32_t GetRenderBufferID();

	private:
		uint32_t m_bufferID = 0; // 帧缓冲的id
		uint32_t m_renderTexture = 0; // 纹理附件的id
		uint32_t m_depthStencilBuffer = 0; // 渲染缓存的id
	};

Primero veamos la implementación de su constructor:

OvRendering::Buffers::Framebuffer::Framebuffer(uint16_t p_width, uint16_t p_height)
{
	/* Generate OpenGL objects */
	glGenFramebuffers(1, &m_bufferID); // 生成帧缓冲id
	glGenTextures(1, &m_renderTexture); // 生成颜色缓冲纹理
	glGenRenderbuffers(1, &m_depthStencilBuffer); // 生成渲染缓存

	// 设置m_renderTexture纹理参数
	glBindTexture(GL_TEXTURE_2D, m_renderTexture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glBindTexture(GL_TEXTURE_2D, 0);

	/* Setup framebuffer */
	Bind();
	// 将纹理设置为渲染目标
	glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_renderTexture, 0);
	Unbind();

	Resize(p_width, p_height);
}

Los objetos de caché de fotograma, textura y renderizado se generan directamente en la construcción, y la textura se asocia con el búfer de fotograma como un archivo adjunto de color. Veamos nuevamente el método de cambio de tamaño.

void OvRendering::Buffers::Framebuffer::Resize(uint16_t p_width, uint16_t p_height)
{
	/* Resize texture */
	// 设置纹理的大小
	glBindTexture(GL_TEXTURE_2D, m_renderTexture);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, p_width, p_height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
	glBindTexture(GL_TEXTURE_2D, 0);

	/* Setup depth-stencil buffer (24 + 8 bits) */
	glBindRenderbuffer(GL_RENDERBUFFER, m_depthStencilBuffer);
	glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_STENCIL, p_width, p_height);
	glBindRenderbuffer(GL_RENDERBUFFER, 0);

	/* Attach depth and stencil buffer to the framebuffer */
	Bind();
	// 配置深度缓冲与模板缓冲
	glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthStencilBuffer);
	glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_depthStencilBuffer);
	Unbind();
}

La suma de estos dos métodos es básicamente la misma que la función de inicio de pseudocódigo anterior, pero está encapsulada de forma orientada a objetos.

3. Principio de selección del ratón

La selección del mouse en Overload primero representa la ID del objeto en la textura, lee el valor de píxel correspondiente en la imagen según la posición del mouse y luego lo decodifica para obtener la ID del objeto. En el cuadro rojo de la figura siguiente se encuentran los tres pasos clave de esta función:

 Primero veamos la función RenderSceneForActorPicking. Esta función renderiza los objetos, cámaras y luces de la escena. Los métodos de renderizado de los tres son muy similares: tomando la cámara de renderizado como ejemplo, el código es el siguiente:

	/* Render cameras */
	for (auto camera : m_context.sceneManager.GetCurrentScene()->GetFastAccessComponents().cameras)
	{
		auto& actor = camera->owner;

		if (actor.IsActive())
		{
            // 对摄像机的id进行编码,设置到Shader的unfiorm中
			PreparePickingMaterial(actor, m_actorPickingMaterial);
			auto& model = *m_context.editorResources->GetModel("Camera");
			auto modelMatrix = CalculateCameraModelMatrix(actor);
            // 绘制摄像机,其覆盖区域的像素全部是其id
			m_context.renderer->DrawModelWithSingleMaterial(model, m_actorPickingMaterial, &modelMatrix);
		}
	}

Existe una función especial PreparePickingMaterial, que convierte los tres bytes del ID en colores y los guarda en la variable u_Diffuse, que se utilizará en el Shader. El código central se muestra en el cuadro rojo en la imagen a continuación. Este método de codificación es un método comúnmente utilizado para escribir información en imágenes y se puede utilizar directamente como referencia.

Definitivamente se necesita Shader para dibujar en FrameBuffer. El Shader de Overload está encapsulado en un material. Se requieren materiales especiales para la selección. La variable m_actorPickingMaterial en la función RenderSceneForActorPicking almacena este material. Rastreamos el código y buscamos la inicialización de esta variable, y podemos encontrar el siguiente código:

/* Picking Material */
auto unlit = m_context.shaderManager[":Shaders\\Unlit.glsl"];
m_actorPickingMaterial.SetShader(unlit);
m_actorPickingMaterial.Set("u_Diffuse", FVector4(1.f, 1.f, 1.f, 1.0f));
m_actorPickingMaterial.Set<OvRendering::Resources::Texture*>("u_DiffuseMap", nullptr);
m_actorPickingMaterial.SetFrontfaceCulling(false);
m_actorPickingMaterial.SetBackfaceCulling(false);

Parece que este Shader está guardado en el archivo Unlit.glsl. También tenga en cuenta que u_DiffuseMap está configurado en nulo. Recuerde esto, esto es intencional y el diablo está escondido en estos detalles.

Abrimos este archivo y analizamos este Shader:

#shader vertex
#version 430 core

layout (location = 0) in vec3 geo_Pos;
layout (location = 1) in vec2 geo_TexCoords;
layout (location = 2) in vec3 geo_Normal;

layout (std140) uniform EngineUBO
{
    mat4    ubo_Model;
    mat4    ubo_View;
    mat4    ubo_Projection;
    vec3    ubo_ViewPos;
    float   ubo_Time;
};

out VS_OUT
{
    vec2 TexCoords;
} vs_out;

void main()
{
    vs_out.TexCoords = geo_TexCoords;

    gl_Position = ubo_Projection * ubo_View * ubo_Model * vec4(geo_Pos, 1.0);
}

#shader fragment
#version 430 core

out vec4 FRAGMENT_COLOR;

in VS_OUT
{
    vec2 TexCoords;
} fs_in;

uniform vec4        u_Diffuse = vec4(1.0, 1.0, 1.0, 1.0);
uniform sampler2D   u_DiffuseMap;
uniform vec2        u_TextureTiling = vec2(1.0, 1.0);
uniform vec2        u_TextureOffset = vec2(0.0, 0.0);

void main()
{
    FRAGMENT_COLOR = texture(u_DiffuseMap, u_TextureOffset + vec2(mod(fs_in.TexCoords.x * u_TextureTiling.x, 1), mod(fs_in.TexCoords.y * u_TextureTiling.y, 1))) * u_Diffuse;
}

No hay mucho que decir sobre el Vertex Shader de este programa de GPU, simplemente calcula las coordenadas NDC de la cuadrícula y listo. Lo que es desconcertante es la última línea de código de Fragment Shader. Permítanme hablar primero de la conclusión: esta línea de código es equivalente a FRAGMENT_COLOR = u_Diffuse. En cuanto a por qué, en pocas palabras, u_DiffuseMap se establece en nulo en la aplicación, pero cuando se pasa a la CPU, la textura con un valor nulo se establece en una textura vacía. El tamaño de esta textura vacía es de un píxel y su valor es blanco puro, por lo que sus resultados de muestreo son todos 1,0.

Consulte el siguiente código para la inicialización del contexto vacío:

 Vea si solo hay un píxel y el valor es 1,0.

Dicho esto, los detalles centrales de la representación de texturas necesarios para la selección están básicamente terminados. Echemos un vistazo a cómo leer esta textura.

Primero obtenga la siguiente posición del mouse. Dado que está dibujado con imgui, la posición absoluta del mouse debe transformarse en la posición relativa de la imagen. Primero vincule FrameBuffer y use glReadPixels para leer los píxeles. Tenga en cuenta que el formato de imagen es RGB, que es consistente con la configuración para inicializar FrameBuffer. Debe prestar atención a estos detalles, y hay muchos misterios. Finalmente, el píxel se decodifica para obtener la identificación del objeto de la escena.

¡Leer código significa comprender estos detalles para que podamos imitarlos y usarlos en nuestros propios proyectos!

Supongo que te gusta

Origin blog.csdn.net/loveoobaby/article/details/133583784
Recomendado
Clasificación