Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: GLSL parser #1

Open
wants to merge 21 commits into
base: master
from

Conversation

Projects
None yet
2 participants
@shuhei
Copy link
Owner

shuhei commented Jul 24, 2018

Build:

cabal build

Test:

# In an Elm project with GLSL in it...
${PATH_TO_ELM_COMPILER}/dist/build/elm/elm make ${ELM_FILE}
parseBlock =
-- TODO: Parse the `{|glsl`
-- TODO: Parse GLSL
-- TODO: Parse the `|}`

This comment has been minimized.

@shuhei

shuhei Jul 24, 2018

Author Owner

This approach might be a bit hard because the GLSL parser needs to check |} everywhere. Another approach is to use Shader.block to find the offset where GLSL ends, and parse only GLSL using the range found already.

This comment has been minimized.

@w0rm

w0rm Jul 25, 2018

Collaborator

I see. In the case of parsing the range we would need to restore the same row and col in the parser's state.

@shuhei

This comment has been minimized.

Copy link
Owner Author

shuhei commented Aug 29, 2018

  • Something else parser: variable declaration without attribute/uniform/varying (eat until ;)
  • Handle functions
  • Repeating combinator
  • Treat #... as //... (single line comment)
@shuhei

This comment has been minimized.

Copy link
Owner Author

shuhei commented Aug 29, 2018

somethingElse :: Parser GLDeclaration
somethingElse =
  do  eatStuff
      PP.oneOf
        [ semicolon
        , curlyPair
        ]
      return SomethingElse
@shuhei

This comment has been minimized.

Copy link
Owner Author

shuhei commented Aug 30, 2018

To clarify different numbers in the parser:

  • offset starts from 0.
  • terminal used to be the remaining length and decremented as the parser proceeds. But now it is the terminal position of the text and shouldn't be changed.
  • row starts from 1.
  • col starts from 1.

The meaning of terminal has changed since we wrote the previous parser. I'm fixing it now, and will push a commit when it's done.

@shuhei

This comment has been minimized.

Copy link
Owner Author

shuhei commented Aug 30, 2018

Done with bffc386

Show resolved Hide resolved compiler/src/Parse/Shader.hs Outdated
Show resolved Hide resolved compiler/src/Parse/Shader.hs Outdated
Show resolved Hide resolved compiler/src/Parse/Shader.hs
Show resolved Hide resolved compiler/src/Parse/Shader.hs Outdated
@shuhei

This comment has been minimized.

Copy link
Owner Author

shuhei commented Aug 30, 2018

Should we care about indent?

Left err ->
cerr err

Right (newOffset, newTerminal, newRow, newCol) ->

This comment has been minimized.

@shuhei

shuhei Aug 30, 2018

Author Owner

Compare offset and newOffset to figure out whether there were one or more spaces or not.

Show resolved Hide resolved compiler/src/Parse/Shader.hs
@shuhei

This comment has been minimized.

Copy link
Owner Author

shuhei commented Aug 31, 2018

For repeating the declaration parser, we can do something like https://github.com/shuhei/elm-compiler/blob/master/compiler/src/Parse/Parse.hs

@w0rm

This comment has been minimized.

Copy link
Collaborator

w0rm commented Aug 31, 2018

@shuhei I cannot compile, I think we need to rebase on top the current version, because it wants to fetch something from the alpha package server.

The following HTTP request failed:
    <https://alpha.elm-lang.org/packages/elm/project-metadata-utils/1.0.0/elm.json>

@shuhei shuhei force-pushed the glsl-parser branch from dc9d882 to 44d54c5 Sep 1, 2018

@shuhei shuhei changed the base branch from master to stable Sep 1, 2018

@shuhei shuhei changed the base branch from stable to master Sep 1, 2018

@shuhei

This comment has been minimized.

Copy link
Owner Author

shuhei commented Sep 1, 2018

@w0rm Rebased this branch against upstream/master. Can you try again?

shuhei and others added some commits Sep 1, 2018

@w0rm

This comment has been minimized.

Copy link
Collaborator

w0rm commented Sep 1, 2018

@shuhei do you think it is ready now?

@@ -23,13 +21,13 @@ import qualified Reporting.Error.Syntax as E
-- SHADER


failure :: Int -> Int -> Text.Text -> Parser a
failure row col msg =
failure :: Int -> Int -> E.ShaderProblem -> Parser a

This comment has been minimized.

@shuhei

shuhei Sep 1, 2018

Author Owner

This is not used for now. Remove this or shaderFailure in Shader.hs.

This comment has been minimized.

@w0rm

w0rm Sep 1, 2018

Collaborator

Removed!

Show resolved Hide resolved compiler/src/Parse/Shader.hs Outdated
Show resolved Hide resolved compiler/src/Parse/Shader.hs
@shuhei

This comment has been minimized.

Copy link
Owner Author

shuhei commented Sep 1, 2018

@w0rm We still need to make whitespaces mandatory in variableDeclaration parser. Otherwise, I think it’s ready to be tested with a lot of shaders!

@shuhei

This comment has been minimized.

Copy link
Owner Author

shuhei commented Sep 1, 2018

It’ll be nice to have an elm file with shaders as test cases. Should we put it into this repo? Or another repo or gist?

@w0rm

This comment has been minimized.

Copy link
Collaborator

w0rm commented Sep 1, 2018

@shuhei I will prepare such file tonight or tomorrow!

@shuhei

This comment has been minimized.

Copy link
Owner Author

shuhei commented Sep 1, 2018

I copied a shader from a shader mentioned in the Elm Slack, and it worked! I had to change mat3 to mat4 and samplerCube to sampler2D though.

complex shader
type alias PbrUniforms =
    { u_LightDirection : Vec3
    , u_LightColor : Vec3
    , u_DiffuseEnvSampler : Texture
    , u_SpecularEnvSampler : Texture
    , u_brdfLUT : Texture
    , u_BaseColorSampler : Texture
    , u_NormalSampler : Texture
    , u_NormalScale : Float
    , u_EmissiveSampler : Texture
    , u_EmissiveFactor : Vec3
    , u_MetallicRoughnessSampler : Texture
    , u_OcclusionSampler : Texture
    , u_OcclusionStrength : Float
    , u_MetallicRoughnessValues : Vec2
    , u_BaseColorFactor : Vec4
    , u_Camera : Vec3
    , u_ScaleDiffBaseMR : Vec4
    , u_ScaleFGDSpec : Vec4
    , u_ScaleIBLAmbient : Vec4
    }


type alias PbrVarying =
    { v_Position : Vec3
    , v_UV : Vec2
    , v_TBN : Mat4
    , v_Normal : Vec3
    }

pbrFragment : Shader {} PbrUniforms PbrVarying
pbrFragment =
    [glsl|
        //
        // This fragment shader defines a reference implementation for Physically Based Shading of
        // a microfacet surface material defined by a glTF model.
        //
        // References:
        // [1] Real Shading in Unreal Engine 4
        //     http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
        // [2] Physically Based Shading at Disney
        //     http://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v3.pdf
        // [3] README.md - Environment Maps
        //     https://github.com/KhronosGroup/glTF-WebGL-PBR/#environment-maps
        // [4] "An Inexpensive BRDF Model for Physically based Rendering" by Christophe Schlick
        //     https://www.cs.virginia.edu/~jdl/bib/appearance/analytic%20models/schlick94b.pdf
        #extension GL_EXT_shader_texture_lod: enable
        #extension GL_OES_standard_derivatives : enable

        precision highp float;

        uniform vec3 u_LightDirection;
        uniform vec3 u_LightColor;

        #ifdef USE_IBL
        // uniform samplerCube u_DiffuseEnvSampler;
        // uniform samplerCube u_SpecularEnvSampler;
        uniform sampler2D u_DiffuseEnvSampler;
        uniform sampler2D u_SpecularEnvSampler;
        uniform sampler2D u_brdfLUT;
        #endif

        #ifdef HAS_BASECOLORMAP
        uniform sampler2D u_BaseColorSampler;
        #endif
        #ifdef HAS_NORMALMAP
        uniform sampler2D u_NormalSampler;
        uniform float u_NormalScale;
        #endif
        #ifdef HAS_EMISSIVEMAP
        uniform sampler2D u_EmissiveSampler;
        uniform vec3 u_EmissiveFactor;
        #endif
        #ifdef HAS_METALROUGHNESSMAP
        uniform sampler2D u_MetallicRoughnessSampler;
        #endif
        #ifdef HAS_OCCLUSIONMAP
        uniform sampler2D u_OcclusionSampler;
        uniform float u_OcclusionStrength;
        #endif

        uniform vec2 u_MetallicRoughnessValues;
        uniform vec4 u_BaseColorFactor;

        uniform vec3 u_Camera;

        // debugging flags used for shader output of intermediate PBR variables
        uniform vec4 u_ScaleDiffBaseMR;
        uniform vec4 u_ScaleFGDSpec;
        uniform vec4 u_ScaleIBLAmbient;

        varying vec3 v_Position;

        varying vec2 v_UV;

        #ifdef HAS_NORMALS
        #ifdef HAS_TANGENTS
        // varying mat3 v_TBN;
        varying mat4 v_TBN;
        #else
        varying vec3 v_Normal;
        #endif
        #endif

        // Encapsulate the various inputs used by the various functions in the shading equation
        // We store values in this struct to simplify the integration of alternative implementations
        // of the shading terms, outlined in the Readme.MD Appendix.
        struct PBRInfo
        {
            float NdotL;                  // cos angle between normal and light direction
            float NdotV;                  // cos angle between normal and view direction
            float NdotH;                  // cos angle between normal and half vector
            float LdotH;                  // cos angle between light direction and half vector
            float VdotH;                  // cos angle between view direction and half vector
            float perceptualRoughness;    // roughness value, as authored by the model creator (input to shader)
            float metalness;              // metallic value at the surface
            vec3 reflectance0;            // full reflectance color (normal incidence angle)
            vec3 reflectance90;           // reflectance color at grazing angle
            float alphaRoughness;         // roughness mapped to a more linear change in the roughness (proposed by [2])
            vec3 diffuseColor;            // color contribution from diffuse lighting
            vec3 specularColor;           // color contribution from specular lighting
        };

        const float M_PI = 3.141592653589793;
        const float c_MinRoughness = 0.04;

        vec4 SRGBtoLINEAR(vec4 srgbIn)
        {
            #ifdef MANUAL_SRGB
            #ifdef SRGB_FAST_APPROXIMATION
            vec3 linOut = pow(srgbIn.xyz,vec3(2.2));
            #else //SRGB_FAST_APPROXIMATION
            vec3 bLess = step(vec3(0.04045),srgbIn.xyz);
            vec3 linOut = mix( srgbIn.xyz/vec3(12.92), pow((srgbIn.xyz+vec3(0.055))/vec3(1.055),vec3(2.4)), bLess );
            #endif //SRGB_FAST_APPROXIMATION
            return vec4(linOut,srgbIn.w);;
            #else //MANUAL_SRGB
            return srgbIn;
            #endif //MANUAL_SRGB
        }

        // Find the normal for this fragment, pulling either from a predefined normal map
        // or from the interpolated mesh normal and tangent attributes.
        vec3 getNormal()
        {
            // Retrieve the tangent space matrix
        #ifndef HAS_TANGENTS
            vec3 pos_dx = dFdx(v_Position);
            vec3 pos_dy = dFdy(v_Position);
            vec3 tex_dx = dFdx(vec3(v_UV, 0.0));
            vec3 tex_dy = dFdy(vec3(v_UV, 0.0));
            vec3 t = (tex_dy.t * pos_dx - tex_dx.t * pos_dy) / (tex_dx.s * tex_dy.t - tex_dy.s * tex_dx.t);

        #ifdef HAS_NORMALS
            vec3 ng = normalize(v_Normal);
        #else
            vec3 ng = cross(pos_dx, pos_dy);
        #endif

            t = normalize(t - ng * dot(ng, t));
            vec3 b = normalize(cross(ng, t));
            mat3 tbn = mat3(t, b, ng);
        #else // HAS_TANGENTS
            mat3 tbn = v_TBN;
        #endif

        #ifdef HAS_NORMALMAP
            vec3 n = texture2D(u_NormalSampler, v_UV).rgb;
            n = normalize(tbn * ((2.0 * n - 1.0) * vec3(u_NormalScale, u_NormalScale, 1.0)));
        #else
            // The tbn matrix is linearly interpolated, so we need to re-normalize
            vec3 n = normalize(tbn[2].xyz);
        #endif

            return n;
        }

        // Calculation of the lighting contribution from an optional Image Based Light source.
        // Precomputed Environment Maps are required uniform inputs and are computed as outlined in [1].
        // See our README.md on Environment Maps [3] for additional discussion.
        #ifdef USE_IBL
        vec3 getIBLContribution(PBRInfo pbrInputs, vec3 n, vec3 reflection)
        {
            float mipCount = 9.0; // resolution of 512x512
            float lod = (pbrInputs.perceptualRoughness * mipCount);
            // retrieve a scale and bias to F0. See [1], Figure 3
            vec3 brdf = SRGBtoLINEAR(texture2D(u_brdfLUT, vec2(pbrInputs.NdotV, 1.0 - pbrInputs.perceptualRoughness))).rgb;
            vec3 diffuseLight = SRGBtoLINEAR(textureCube(u_DiffuseEnvSampler, n)).rgb;

        #ifdef USE_TEX_LOD
            vec3 specularLight = SRGBtoLINEAR(textureCubeLodEXT(u_SpecularEnvSampler, reflection, lod)).rgb;
        #else
            vec3 specularLight = SRGBtoLINEAR(textureCube(u_SpecularEnvSampler, reflection)).rgb;
        #endif

            vec3 diffuse = diffuseLight * pbrInputs.diffuseColor;
            vec3 specular = specularLight * (pbrInputs.specularColor * brdf.x + brdf.y);

            // For presentation, this allows us to disable IBL terms
            diffuse *= u_ScaleIBLAmbient.x;
            specular *= u_ScaleIBLAmbient.y;

            return diffuse + specular;
        }
        #endif

        // Basic Lambertian diffuse
        // Implementation from Lambert's Photometria https://archive.org/details/lambertsphotome00lambgoog
        // See also [1], Equation 1
        vec3 diffuse(PBRInfo pbrInputs)
        {
            return pbrInputs.diffuseColor / M_PI;
        }

        // The following equation models the Fresnel reflectance term of the spec equation (aka F())
        // Implementation of fresnel from [4], Equation 15
        vec3 specularReflection(PBRInfo pbrInputs)
        {
            return pbrInputs.reflectance0 + (pbrInputs.reflectance90 - pbrInputs.reflectance0) * pow(clamp(1.0 - pbrInputs.VdotH, 0.0, 1.0), 5.0);
        }

        // This calculates the specular geometric attenuation (aka G()),
        // where rougher material will reflect less light back to the viewer.
        // This implementation is based on [1] Equation 4, and we adopt their modifications to
        // alphaRoughness as input as originally proposed in [2].
        float geometricOcclusion(PBRInfo pbrInputs)
        {
            float NdotL = pbrInputs.NdotL;
            float NdotV = pbrInputs.NdotV;
            float r = pbrInputs.alphaRoughness;

            float attenuationL = 2.0 * NdotL / (NdotL + sqrt(r * r + (1.0 - r * r) * (NdotL * NdotL)));
            float attenuationV = 2.0 * NdotV / (NdotV + sqrt(r * r + (1.0 - r * r) * (NdotV * NdotV)));
            return attenuationL * attenuationV;
        }

        // The following equation(s) model the distribution of microfacet normals across the area being drawn (aka D())
        // Implementation from "Average Irregularity Representation of a Roughened Surface for Ray Reflection" by T. S. Trowbridge, and K. P. Reitz
        // Follows the distribution function recommended in the SIGGRAPH 2013 course notes from EPIC Games [1], Equation 3.
        float microfacetDistribution(PBRInfo pbrInputs)
        {
            float roughnessSq = pbrInputs.alphaRoughness * pbrInputs.alphaRoughness;
            float f = (pbrInputs.NdotH * roughnessSq - pbrInputs.NdotH) * pbrInputs.NdotH + 1.0;
            return roughnessSq / (M_PI * f * f);
        }

        void main()
        {
            // Metallic and Roughness material properties are packed together
            // In glTF, these factors can be specified by fixed scalar values
            // or from a metallic-roughness map
            float perceptualRoughness = u_MetallicRoughnessValues.y;
            float metallic = u_MetallicRoughnessValues.x;
        #ifdef HAS_METALROUGHNESSMAP
            // Roughness is stored in the 'g' channel, metallic is stored in the 'b' channel.
            // This layout intentionally reserves the 'r' channel for (optional) occlusion map data
            vec4 mrSample = texture2D(u_MetallicRoughnessSampler, v_UV);
            perceptualRoughness = mrSample.g * perceptualRoughness;
            metallic = mrSample.b * metallic;
        #endif
            perceptualRoughness = clamp(perceptualRoughness, c_MinRoughness, 1.0);
            metallic = clamp(metallic, 0.0, 1.0);
            // Roughness is authored as perceptual roughness; as is convention,
            // convert to material roughness by squaring the perceptual roughness [2].
            float alphaRoughness = perceptualRoughness * perceptualRoughness;

            // The albedo may be defined from a base texture or a flat color
        #ifdef HAS_BASECOLORMAP
            vec4 baseColor = SRGBtoLINEAR(texture2D(u_BaseColorSampler, v_UV)) * u_BaseColorFactor;
        #else
            vec4 baseColor = u_BaseColorFactor;
        #endif

            vec3 f0 = vec3(0.04);
            vec3 diffuseColor = baseColor.rgb * (vec3(1.0) - f0);
            diffuseColor *= 1.0 - metallic;
            vec3 specularColor = mix(f0, baseColor.rgb, metallic);

            // Compute reflectance.
            float reflectance = max(max(specularColor.r, specularColor.g), specularColor.b);

            // For typical incident reflectance range (between 4% to 100%) set the grazing reflectance to 100% for typical fresnel effect.
            // For very low reflectance range on highly diffuse objects (below 4%), incrementally reduce grazing reflecance to 0%.
            float reflectance90 = clamp(reflectance * 25.0, 0.0, 1.0);
            vec3 specularEnvironmentR0 = specularColor.rgb;
            vec3 specularEnvironmentR90 = vec3(1.0, 1.0, 1.0) * reflectance90;

            vec3 n = getNormal();                             // normal at surface point
            vec3 v = normalize(u_Camera - v_Position);        // Vector from surface point to camera
            vec3 l = normalize(u_LightDirection);             // Vector from surface point to light
            vec3 h = normalize(l+v);                          // Half vector between both l and v
            vec3 reflection = -normalize(reflect(v, n));

            float NdotL = clamp(dot(n, l), 0.001, 1.0);
            float NdotV = clamp(abs(dot(n, v)), 0.001, 1.0);
            float NdotH = clamp(dot(n, h), 0.0, 1.0);
            float LdotH = clamp(dot(l, h), 0.0, 1.0);
            float VdotH = clamp(dot(v, h), 0.0, 1.0);

            PBRInfo pbrInputs = PBRInfo(
                NdotL,
                NdotV,
                NdotH,
                LdotH,
                VdotH,
                perceptualRoughness,
                metallic,
                specularEnvironmentR0,
                specularEnvironmentR90,
                alphaRoughness,
                diffuseColor,
                specularColor
            );

            // Calculate the shading terms for the microfacet specular shading model
            vec3 F = specularReflection(pbrInputs);
            float G = geometricOcclusion(pbrInputs);
            float D = microfacetDistribution(pbrInputs);

            // Calculation of analytical lighting contribution
            vec3 diffuseContrib = (1.0 - F) * diffuse(pbrInputs);
            vec3 specContrib = F * G * D / (4.0 * NdotL * NdotV);
            // Obtain final intensity as reflectance (BRDF) scaled by the energy of the light (cosine law)
            vec3 color = NdotL * u_LightColor * (diffuseContrib + specContrib);

            // Calculate lighting contribution from image based lighting source (IBL)
        #ifdef USE_IBL
            color += getIBLContribution(pbrInputs, n, reflection);
        #endif

            // Apply optional PBR terms for additional (optional) shading
        #ifdef HAS_OCCLUSIONMAP
            float ao = texture2D(u_OcclusionSampler, v_UV).r;
            color = mix(color, color * ao, u_OcclusionStrength);
        #endif

        #ifdef HAS_EMISSIVEMAP
            vec3 emissive = SRGBtoLINEAR(texture2D(u_EmissiveSampler, v_UV)).rgb * u_EmissiveFactor;
            color += emissive;
        #endif

            // This section uses mix to override final color for reference app visualization
            // of various parameters in the lighting equation.
            color = mix(color, F, u_ScaleFGDSpec.x);
            color = mix(color, vec3(G), u_ScaleFGDSpec.y);
            color = mix(color, vec3(D), u_ScaleFGDSpec.z);
            color = mix(color, specContrib, u_ScaleFGDSpec.w);

            color = mix(color, diffuseContrib, u_ScaleDiffBaseMR.x);
            color = mix(color, baseColor.rgb, u_ScaleDiffBaseMR.y);
            color = mix(color, vec3(metallic), u_ScaleDiffBaseMR.z);
            color = mix(color, vec3(perceptualRoughness), u_ScaleDiffBaseMR.w);

            gl_FragColor = vec4(pow(color,vec3(1.0/2.2)), baseColor.a);
        }
    |]

shuhei and others added some commits Sep 1, 2018

Merge pull request #3 from shuhei/allow-only-single-byte-chars
Disallow multibyte characters in GLSL variable names
@w0rm

This comment has been minimized.

Copy link
Collaborator

w0rm commented Sep 1, 2018

@shuhei I'm afraid I won't be able to find a more complicated shader from my examples :D

w0rm and others added some commits Sep 1, 2018

Merge pull request #4 from shuhei/simplify-whitespace
Simplify whitespace handling
@w0rm

This comment has been minimized.

Copy link
Collaborator

w0rm commented Sep 2, 2018

@shuhei I actually tried to compile all my games with this version of compiler, and it worked fine. The shaders I have are much less complex that the one you suggested, so I don't think we should test them, this should be fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.