Shaders

From Pragma
Jump to: navigation, search

Description

Custom shaders in pragma consist of two parts, a lua-script and a glsl- or hlsl-program, which means you'll need some knowledge of the OpenGL Shading Language or High Level Shading Language.

Lua-Script

The lua-part is currently very limited, but will be expanded in the future. All lua-shaders should be put in a lua-file in "lua/shaders/", which will be loaded automatically by the game.
A simple lua-shader could look like this:

--[[
	The class name for this shader, and the base class this shader is derived from.
]]
util.register_class("ShaderTest","BaseShaderTextured")

--[[
	The paths to the shader stage files. These are relative to the "shaders" directory of the pragma installation.
	It is recommended that you DON'T add the ".gls" extension to these! If you add the extension, the shaders will be recompiled every time.
	Without the extension, the compiled ".spv" files will be prefered (Resulting in faster load times), and the shaders will only be
	recompiled, if no ".spv"-files exist.
]]
ShaderTest.FragmentShader = "lua/fs_test" -- Our fragment shader, located in "<root>/shaders/lua/fs_test.gls"
ShaderTest.VertexShader = "world/vs_textured" -- Use the default textured vertex shader
--ShaderTest.GeometryShader = "" -- We don't have a geometry shader

function ShaderTest:__init()
	BaseShaderTextured.__init(self) -- Base constructor
end

function ShaderTest:InitializeMaterial(mat)
	BaseShaderTextured.InitializeMaterial(self,mat) -- Just use the default material initialization
end

function ShaderTest:Draw(mesh)
	BaseShaderTextured.Draw(self,mesh) -- Default rendering
end
shader.register("testshader",ShaderTest)

This will enable the use of the shader "testshader", which can, for instance, be used in materials. All objects which use such a material will be rendered using this shader.
The lua-shader tells the game what should be rendered, but not how. The 'how' is determined by the glsl program (Which is run on the GPU).

GLSL Program

Here's an example for a simple fragment shader, which will render a regular, textured (and lit) mesh:

#version 440

#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable

#include "../modules/fs_world.gls"
#include "../modules/sh_csm.gls"
#include "../modules/sh_parallaxmapping.gls"
#include "../debug/fs_debug_csm.gls"

void main()
{
	vec2 texCoords = apply_parallax(use_parallax_map(),fs_in.vert_uv); // Transform UV coordinates in case a parallax texture is used
	fs_color = get_processed_color(get_diffuse_map_color(texCoords),texCoords); // Apply lighting effects to diffuse texture fragment
	fs_color = debug_get_csm_cascade_color(fs_color); // Debugging colors for cascaded shadow mapping, if enabled
	extract_bright_color(fs_color); // Extracts overly bright areas for bloom effects
}

All shader code is located inside ".gls"-files inside the "shaders" directory. These can be opened with a simple text-editor. If you're using Notepad++, you can use the Notepad++ GLSL 4.3 Syntax Highlight plugin to make the files easier on the eyes. For sublime, install the "sublime-glsl" package.

Reload shader program

Changing your glsl code and reloading the map or game, does not necessarily reload the shader. In general, the game will compile your shader code the first time it's used, and save the compiled shader as ".spv"-file next to your ".gls" code. The ".spv"-file will be used in the future directly, because it's a lot faster to load.
You can either remove this ".spv" file to force the game to reload the shader, or use the "shader_reload" command.</br> The "shader_reload" command can be used any time ingame (As long as your shader has been initialized) and only reloads the shader code (Not the lua-code!). If you use the command without any arguments, all initialized shaders will be reloaded one by one. To reload a specific shader, use "shader_reload <shaderName>" (e.g. "shader_reload testshader"). If no output is generated, your shader has been recompiled and reloaded successfully. Otherwise it will display an error log.

Error Handling

If your shader couldn't be compiled or linked, an error log will be displayed in the console. A typical error log looks as such:

> shader_reload test_shader
Reloading shader 'test_shader'...
Unable to load shader 'test_shader':
Shader Stage: Fragment
Shader File: "shaders\lua/fs_test.gls"
Warning, version 440 is not yet complete; most version-specific features are present, but some are missing.
WARNING: 0:8: 'shaders\lighting\fs_shadow.gls': '#extension' : extension not supported: GL_EXT_texture_array
    
    // Required for CSM / PSSM -> standard in shader model 400 and above
  > #extension GL_EXT_texture_array : enable
    
    #define SOFT_SHADOW_DISABLED 0
ERROR: 0:23: 'shaders\lua\fs_test.gls': '=' :  cannot convert from 'temp 4-component vector of float' to 'temp 3-component vector of float'
    vec4 color2 = vec4(1.0 -color1.r,1.0 -color1.g, 1.0 -color1.b,color1.a);
    
  > vec3 color = color1 *v +color2 *(1.0 -v);
    fs_color = get_processed_color(color,texCoords);
    fs_color = debug_get_csm_cascade_color(fs_color);
ERROR: 0:24: 'shaders\lua\fs_test.gls': 'get_processed_color' : no matching overloaded function found
    
    vec3 color = color1 *v +color2 *(1.0 -v);
  > fs_color = get_processed_color(color,texCoords);
    fs_color = debug_get_csm_cascade_color(fs_color);
    extract_bright_color(fs_color);
ERROR: 0:24: 'shaders\lua\fs_test.gls': 'assign' :  cannot convert from 'const float' to 'layout(location=0 ) out 4-component vector of float'
    
    vec3 color = color1 *v +color2 *(1.0 -v);
  > fs_color = get_processed_color(color,texCoords);
    fs_color = debug_get_csm_cascade_color(fs_color);
    extract_bright_color(fs_color);
ERROR: 3 compilation errors.  No code generated.

Explanation:

  • Shader File: "shaders\lua/fs_test.gls"

This is the main shader file for the shader stage that invoked the error (Usually the file with contains the "main" function). This is not necessarily the file that actually caused the error!

  • WARNING: 0:8: 'shaders\lighting\fs_shadow.gls': '#extension' : extension not supported: GL_EXT_texture_array

Warnings generally don't prevent your shader from compiling, but you should take care of them anyway.

  • ERROR: 0:23: 'shaders\lua\fs_test.gls': '=' : cannot convert from 'temp 4-component vector of float' to 'temp 3-component vector of float'

In general you should work your way through the errors starting at the top, since one error has a good chance of triggering others further down the line.
If your shader contains an error, the compilation will fail and the shader cannot be used. The syntax for an error is:
ERROR: 0:<line>: <file>: <message>
This should allow you to easily locate the source of the error. Additionally, a few lines of code are displayed below the error line, which represent the code surrounding the line which caused the error. The actual source of the error is prepended with a '>'.

Includes

The preprocessor directive "#include" is not a standard glsl directive. It allows you to include another ".gls" file within the "shaders" directory.

Defines

Pragma provides several defines in addition to the standard glsl definitions:

  • GLS_FRAGMENT_SHADER: Set to 1 if the current file is being used in a fragment shader
  • GLS_VERTEX_SHADER: Set to 1 if the current file is being used in a vertex shader
  • GLS_GEOMETRY_SHADER: Set to 1 if the current file is being used in a geometry shader
  • GLS_COMPUTE_SHADER: Set to 1 if the current file is being used in a compute shader

Naming conventions

Technically it doesn't matter what you name your shader files, but for consistency, you should stick to these conventions:

  • Prepend fragment shaders with the "fs_" prefix
  • Prepend vertex shaders with the "vs_" prefix
  • Prepend geometry shaders with the "gs_" prefix
  • Prepend compute shaders with the "cs_" prefix
  • Use the "sh_" prefix for include-files which can be used for different shader stages (e.g. fragment and vertex shaders)
  • Use the ".gls" extension for all GLSL shader files
  • Place custom shaders into "shaders/lua/*"

HLSL Program

Everything that applies for GLSL Shaders also applies for HLSL shaders, except for a few differences:

  • The extension for HLSL shaders has to be ".hls"
  • If a .gls-file with the same name exists, it will be used instead of the .hls-file
  • Do not mix GLSL and HLSL for different shader stages (And do not include .gls-files in a .hls file, and the other way around)

Defines

These are the same as described in the GLSL Defines section, except with the "HLS_"-prefix instead of "GLS_":

  • HLS_FRAGMENT_SHADER: Set to 1 if the current file is being used in a pixel shader
  • HLS_VERTEX_SHADER: Set to 1 if the current file is being used in a vertex shader
  • HLS_GEOMETRY_SHADER: Set to 1 if the current file is being used in a geometry shader
  • HLS_COMPUTE_SHADER: Set to 1 if the current file is being used in a compute shader