Eu estou tentando envolver minha cabeça em torno de como os sistemas de materiais como esse , isso são implementadas. Esses sistemas poderosos e fáceis de usar, semelhantes a gráficos, parecem ser relativamente comuns como um método para permitir que programadores e não programadores criem shaders rapidamente. No entanto, da minha experiência relativamente limitada com programação gráfica, não tenho muita certeza de como eles funcionam.
Fundo:
Portanto, quando eu programei sistemas simples de renderização OpenGL antes, normalmente crio uma classe Material que carrega, compila e vincula shaders de arquivos GLSL estáticos que eu criei manualmente. Também costumo criar essa classe como um invólucro simples para acessar variáveis uniformes GLSL. Como um exemplo simples, imagine que eu tenho um shader de vértice básico e um shader de fragmento, com um Texture2D extra uniforme para passar uma textura. Minha classe Material simplesmente carregava e compilava esses dois shaders em um material e, a partir desse momento, expunha uma interface simples para ler / escrever o uniforme Texture2D desse shader.
Para tornar esse sistema um pouco mais flexível, costumo escrevê-lo de uma maneira que me permita passar uniformes de qualquer nome / tipo [ie: SetUniform_Vec4 ("AmbientColor", colorVec4); que definiria o uniforme AmbientColor como um vetor 4d específico chamado "colorVec4" se esse uniforme existir no 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..
}
Isso funciona, mas parece um sistema ruim, devido ao fato de que o cliente da classe Material precisa acessar os uniformes apenas pela fé - o usuário deve estar um pouco ciente dos uniformes que estão em cada objeto material, porque eles são forçados a passe-os pelo nome GLSL. Não é um grande negócio quando apenas 1-2 pessoas trabalham com o sistema, mas não consigo imaginar que esse sistema fosse muito bem dimensionado e, antes de fazer minha próxima tentativa de programar um sistema de renderização OpenGL, quero nivelar um pouco.
Questão:
É onde estou até agora, então tenho tentado estudar como outros mecanismos de renderização lidam com seus sistemas de materiais.
Essa abordagem baseada em nós é ótima e parece ser um sistema extremamente comum para a criação de sistemas de materiais fáceis de usar em ferramentas e motores modernos. Pelo que posso dizer, eles são baseados em uma estrutura de dados gráficos, onde cada nó representa algum aspecto de sombreamento do seu material e cada caminho representa algum tipo de relacionamento entre eles.
Pelo que sei, implementar esse tipo de sistema seria uma classe MaterialNode tão simples com uma variedade de subclasses (TextureNode, FloatNode, LerpNode, etc.). Onde cada subclasse MaterialNode teria MaterialConnections.
class MaterialConnection
{
MatNode_Out * fromNode;
MatNode_In * toNode;
}
class LerpNode : MaterialNode
{
MatNode_In x;
MatNode_In y;
MatNode_In alpha;
MatNode_Out result;
}
Essa é a ideia muito básica , mas estou um pouco incerto sobre como alguns aspectos desse sistema funcionariam:
1.) Se você observar as várias 'Expressões materiais' (nós) que o Unreal Engine 4 usa , verá que cada uma delas possui conexões de entrada e saída de vários tipos. Alguns nós flutuam de saída, alguns vetores de saída2, alguns vetores de saída4 etc. Como posso melhorar os nós e as conexões acima para que eles possam suportar uma variedade de tipos de entrada e saída? A subclasse de MatNode_Out com MatNode_Out_Float e MatNode_Out_Vec4 (e assim por diante) seria uma escolha sábia?
2.) Finalmente, como esse tipo de sistema se relaciona aos shaders GLSL? Olhando novamente para UE4 (e da mesma forma para os outros sistemas vinculados acima), é necessário que o usuário conecte algum nó de material a um nó grande com vários parâmetros que representam parâmetros de sombreador (cor base, metalidade, brilho, emissividade etc.) . Minha suposição original era que o UE4 tinha algum tipo de 'shader mestre' codificado com uma variedade de uniformes, e tudo o que o usuário faz em seu 'material' é simplesmente passado para o 'shader mestre' quando eles conectam seus nós ao ' nó principal '.
No entanto, a documentação UE4 afirma:
"Cada nó contém um trecho de código HLSL, designado para executar uma tarefa específica. Isso significa que, ao construir um Material, você está criando um código HLSL através de scripts visuais."
Se isso for verdade, esse sistema gera um script de sombreador real? Como, exatamente, isso funciona?