1
\$\begingroup\$

At first, I thought that they are the same, shaders in game editor write by programmers(or use some graph flow tools nowadays) just get compiled into opengl shaders.

But now I'm writing my own render engine, and found that my engine's shader should not be replaced, since it will do the coordinate transformations.

So, is a shader in Unity/UE the same thing as a shader is in Opengl?

In other words, does the shader in game editor directly change the shaders in the pipeline, or it just use some technical to insert into the render's own shaders?

\$\endgroup\$
1
\$\begingroup\$

shaders in game editor written by programmers just get compiled into opengl shaders.

Yes, this is exactly the case. At the end of the day, whatever you write in Unity ShaderLab or build in a shader graph ultimately gets compiled down to a standard hlsl/glsl/vulkan/metal shader program (depending on your target platform).

Parts of the shader that need to work in a consistent way to play nicely with the engine's conventions are handled in a few different ways, from least to most complex:

  • Built-in variables and functions: Unity provides many pre-defined uniform variables, helper functions, and structs that you can access via an #include "UnityCG.cginc" directive.

    Want the coordinate transformation to apply for the current object? That's available, pre-populated by the engine into a variable called unity_ObjectToWorld. Want to project it through the current camera, that's UNITY_MATRIX_MVP. Need the position and projection parameters of the current camera? They're available as _WorldSpaceCameraPos and _ProjectionParams. These and many more are listed here

  • Preprocessor macros: Some operations are a bit more complicated than a pre-defined variable or function, because the data types or steps of the process might be different depending on the platform, or the rendering settings, or whether you're in VR, etc.

    For these cases, Unity provides macros that are recognized by its shader compiler, and expanded into the appropriate shader code depending on the context of the shader version being compiled.

    These include things like UnityObjectToClipPos() which handles transforming vertices (a little more efficiently than multiplying out the matrices yourself, since it's aware of the batching/instancing Unity uses internally), or LinearEyeDepth(i) for decoding a sample from a depth map, whatever encoding it might use on the target platform/graphics settings.

  • Shader templates: Working consistently with lighting can be a challenge. You'd have to copy a lot of shading boilerplate into every fragment shader, and remember to call all the right macros to get the same physically-based results as the built-in ones, under all lighting scenarios, even if you only wanted to change one small detail.

    To help with this, Unity introduces what it calls a "Surface Shader". Here, you don't write a vertex or fragment program, but a "surface program," whose job is to populate a data structure with the albedo, metalness, roughness, emission, and normal of the shaded point — but not worry about any of the shading. Under the hood, when Unity compiles a vertex/fragment shader from this, it inserts your surface logic into the appropriate place in its built-in lighting shader template for each lighting configuration needed.

    This lets you get consistent lighting & shading without worrying about that complexity, and your code describing the surface properties is portable between different lighting and shading models.

All that applies to shaders that you write in text. For node-graph shaders, the situation is much the same, except all the tricks above just manifest as nodes. Nodes that access useful pre-defined uniforms. Function nodes that compile down to different code depending on the platform/scenario. Shader root nodes represent different template shaders, that expose the parameters you're allowed to modify, and compile that code into the boilerplate for the parts that are standardized.

\$\endgroup\$
5
  • \$\begingroup\$ Hi, correct if I'm wrong: though a programmer(code) or a designer(graph tools) create shaders, but the final shader is still manipulated by the editor to make sure all is well, right? That's why even though I didn't do the coordinate transformation in the shader, the coordinate system still works fine? \$\endgroup\$ – ravenisadesk May 30 '20 at 14:45
  • \$\begingroup\$ This can happen in a few ways, as described above. If I make a shader graph, I'm usually just providing input for the surface properties, and the coordinate transformation is part of the shader template that this graph code is inserted into as it's compiled. If I'm writing a vertex-fragment shader by hand, I still have to include the coordinate transformation code, but the engine can make it easier for me by pre-populating variables with the matrices I need, or giving me a convenience function to transform to clip space for the current rendering setup. \$\endgroup\$ – DMGregory May 30 '20 at 14:48
  • \$\begingroup\$ Thanks so much, I seem to understand a bit. \$\endgroup\$ – ravenisadesk May 30 '20 at 14:55
  • \$\begingroup\$ If you have trouble applying this to your engine, try asking a separate question about your engine, providing us with details of how you're approaching the problem so far. \$\endgroup\$ – DMGregory May 30 '20 at 15:01
  • \$\begingroup\$ That's very nice. I'm good at writing compilers, currently my plan is to write a parser to change a graph/custom code to the actual GLSL code. I'll come back if I get troubles. Thanks again for your help. \$\endgroup\$ – ravenisadesk May 30 '20 at 15:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.