Estoy tratando de envolver mi cabeza en torno a cómo los sistemas materiales como este , esta aplicación. Estos sistemas potentes y fáciles de usar, similares a gráficos, parecen ser relativamente comunes como un método para permitir que programadores y no programadores creen rápidamente sombreadores. Sin embargo, desde mi experiencia relativamente limitada con la programación de gráficos, no estoy completamente seguro de cómo funcionan.
Antecedentes:
Entonces, cuando he programado sistemas simples de renderizado OpenGL antes, normalmente creo una clase de Material que carga, compila y vincula sombreadores a partir de archivos GLSL estáticos que he creado manualmente. También suelo crear esta clase como un contenedor simple para acceder a las variables uniformes GLSL. Como un simple ejemplo, imagine que tengo un sombreador de vértices básico y un sombreador de fragmentos, con un Texture2D extra uniforme para pasar una textura. Mi clase de Material simplemente cargaría y compilaría esos dos sombreadores en un material, y desde ese momento expondría una interfaz simple para leer / escribir el uniforme Texture2D de ese sombreador.
Para hacer que este sistema sea un poco más flexible, generalmente lo escribo de una manera que me permite intentar pasar uniformes de cualquier nombre / tipo [es decir: SetUniform_Vec4 ("AmbientColor", colorVec4); que establecería el uniforme AmbientColor en un vector 4d particular llamado "colorVec4" si ese uniforme existe en el material] .
class Material
{
private:
int shaderID;
string vertShaderPath;
string fragSahderPath;
void loadShaderFiles(); //load shaders from files at internal paths.
void buildMaterial(); //link, compile, buffer with OpenGL, etc.
public:
void SetGenericUniform( string uniformName, int param );
void SetGenericUniform( string uniformName, float param );
void SetGenericUniform( string uniformName, vec4 param );
//overrides for various types, etc...
int GetUniform( string uniformName );
float GetUniform( string uniformName );
vec4 GetUniform( string uniformName );
//etc...
//ctor, dtor, etc., omitted for clarity..
}
Esto funciona, pero se siente como un mal sistema debido al hecho de que el cliente de la clase Material tiene que acceder a los uniformes solo por fe: el usuario debe ser algo consciente de los uniformes que hay en cada objeto material porque se ven obligados a páselos por su nombre GLSL. No es un gran problema cuando solo 1-2 personas trabajan con el sistema, pero no puedo imaginar que este sistema escale muy bien, y antes de hacer mi próximo intento de programar un sistema de renderizado OpenGL, quiero nivelar Subir un poco.
Pregunta:
Ahí es donde estoy hasta ahora, así que he estado tratando de estudiar cómo otros motores de procesamiento manejan sus sistemas de materiales.
Este enfoque basado en nodos es excelente y parece ser un sistema extremadamente común para crear sistemas de materiales fáciles de usar en motores y herramientas modernos. Por lo que puedo decir, se basan en una estructura de datos gráficos donde cada nodo representa un aspecto sombreado de su material y cada ruta representa algún tipo de relación entre ellos.
Por lo que puedo decir, implementar ese tipo de sistema sería tan simple como una clase MaterialNode con una variedad de subclases (TextureNode, FloatNode, LerpNode, etc.). Donde cada subclase de MaterialNode tendría MaterialConnections.
class MaterialConnection
{
MatNode_Out * fromNode;
MatNode_In * toNode;
}
class LerpNode : MaterialNode
{
MatNode_In x;
MatNode_In y;
MatNode_In alpha;
MatNode_Out result;
}
Esa es la idea básica , pero no estoy seguro de cómo funcionarían algunos aspectos de este sistema:
1.) Si observa las diversas 'Expresiones de materiales' (nodos) que utiliza Unreal Engine 4 , verá que cada una tiene conexiones de entrada y salida de una variedad de tipos. Algunos nodos de salida flotantes, algunos de salida vector2, algunos de salida vector4, etc. ¿Cómo puedo mejorar los nodos y las conexiones anteriores para que puedan admitir una variedad de tipos de entrada y salida? ¿Sería una buena elección subclasificar MatNode_Out con MatNode_Out_Float y MatNode_Out_Vec4 (y así sucesivamente)?
2.) Finalmente, ¿cómo se relaciona este tipo de sistema con los sombreadores GLSL? Mirando nuevamente a UE4 (y de manera similar para los otros sistemas vinculados anteriormente), se requiere que el usuario conecte algún nodo de material en un nodo grande con varios parámetros que representan parámetros de sombreado (color base, metalidad, brillo, emisividad, etc.) . Mi suposición original era que UE4 tenía algún tipo de 'sombreador maestro' codificado con una variedad de uniformes, y todo lo que el usuario hace en su 'material' simplemente se pasa al 'sombreador maestro' cuando conectan sus nodos en el ' nodo maestro '.
Sin embargo, la documentación de UE4 establece:
"Cada nodo contiene un fragmento de código HLSL, designado para realizar una tarea específica. Esto significa que a medida que construye un Material, está creando código HLSL a través de secuencias de comandos visuales".
Si eso es cierto, ¿este sistema genera un script de sombreador real? ¿Cómo funciona esto exactamente?