Created
February 6, 2019 01:51
Revisions
-
Jonathan Sandusky created this gist
Feb 6, 2019 .There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,380 @@ //========================================================== // Toon Shader //========================================================== // Features: // - artist supplied *tone* ramp // - Control map to force shadow/highlight // - model-space vertical 1px shade control (hat shadows, etc) // - MSDF *linework* map // - rimlight glow // - geometry-shader outlines // - flaky about normals, hates most *stock* models // - matcap/spherical environment maps // - hair *halo* rings // //========================================================== // Configuration //========================================================== // #define TOON_HAIR_RING || TOON_HAIR_RING_UV2 // includes a mat-cap like hair ring, requires TEXCOORD1 for the vertical coordinate, U is ignored // HAIR_RING and MATCAP are mutually exclusive // #define TOON_MATCAP // includes a mat-cap for metals // MATCAP and HAIR_RING are mutually exclusive // #define TOON_RIMLIGHT // includes rim-lighting features // #define TOON_VERTICAL_RAMP // includes a vertically sampled 1d texture for controlled dimming, like an AO map // model-space Y coordinate is used to sample // #define TOON_CONTROL_MAP // has a control map // #define TOON_RIMLIGHT // #define TOON_LINE_MAP || TOON_LINE_MAP_CONTROLLED // MSDF linework map // #define TOON_GS_OUTLINE // Use geometry shader to emit reversed faces colored black for lines // #define TOON_TESS // Use PN tessellation // #define NORMALMAP is supported // BEWARE of noisy normals // #define SPECMAP is supported // However, it is used *straight* and not stepped like diffuse // #define ALPHAMASK is supported // #define EMISSIVEMAP is supported, but means a ControlMap cannot be used // Textures // Required: tone map // remaps the acquired lighting values, responsible for the overal shade tone // Optional: control map // R channel: min lighting sample, use to force highlights // G channel: max lighting sample, use to ban highlights // B channel: outline power, scales the outline thickness // A channel: matcap mask // Optional: rimlight map // RGB color // A channel: rim-light mask // Optional: linework map // RGB channels: MSDF // optional A channel: minimum dot-product to gate lines // only used with TOON_LINE_MAP_CONTROLLED // Optional: vertical ramp // New Uniforms // cHairRingTransform: XY = offset, ZW = scale // cVerticalRampShift: X = lower, Y = upper // cRimLightColor: RGB = color, A = power // cLineWorkPower: XY = msdf scales, Z = multiplier // cOutlinePower: X = scale along normal, Y = offset along camera vector // Reused Uniforms // cMatSpecColor: as-is // cMatEnvMapColor: multiplied with matcap map #include "Uniforms.hlsl" #include "Constants.hlsl" #include "Samplers.hlsl" #include "Transform.hlsl" #include "Lighting.hlsl" #include "Fog.hlsl" #include "Toon_Data.hlsl" #include "Toon_Util.hlsl" struct VSOutput { #ifndef NORMALMAP float2 oTexCoord : TEXCOORD0; #else float4 oTexCoord : TEXCOORD0; float4 oTangent : TEXCOORD3; #endif float4 oPosition : OUTPOSITION; float3 oNormal : TEXCOORD1; float4 oWorldPos : TEXCOORD2; #if defined(TOON_GS_OUTLINE) float4 oColor : COLOR0; #endif #ifdef PERPIXEL #ifdef SHADOW float4 oShadowPos[NUMCASCADES] : TEXCOORD4; #endif #ifdef SPOTLIGHT float4 oSpotPos : TEXCOORD5; #endif #ifdef POINTLIGHT float3 oCubeMaskVec : TEXCOORD5; #endif #else float3 oVertexLight : COLOR1; #endif #if defined(D3D11) && defined(CLIPPLANE) float oClip : SV_CLIPDISTANCE0; #endif #if defined(TOON_VERTICAL_RAMP) float oRampCoord : TEXCOORD6; #endif #if defined(TOON_HAIR_RING) || defined(TOON_HAIR_RING_UV2) float2 oHairUV : TEXCOORD7; #endif #if defined(TOON_MATCAP) float2 oMatCapUV : TEXCOORD7; #endif #if defined(TOON_GS_OUTLINE) float4 oOutlineOffset : TEXCOORD8; #endif }; #if defined(COMPILEVS) VSOutput VS( float4 iPos : POSITION, float3 iNormal : NORMAL, float4 iTangent : TANGENT, float2 iTexCoord : TEXCOORD0 #ifdef SKINNED , float4 iBlendWeights : BLENDWEIGHT , int4 iBlendIndices : BLENDINDICES #endif #ifdef INSTANCED , float4x3 iModelInstance : TEXCOORD4 #endif #if defined(TOON_HAIR_RING_UV2) , float2 iHairUV : TEXCOORD1 #endif ) { VSOutput ret = (VSOutput)0; float4x3 modelMatrix = iModelMatrix; #if defined(TOON_VERTICAL_RAMP) ret.oRampCoord = (iPos.y - cVerticalRampShift.x) / (cVerticalRampShift.y - cVerticalRampShift.x) + cVerticalRampShift.z; #endif float3 worldPos = GetWorldPos(modelMatrix); ret.oPosition = GetClipPos(worldPos); ret.oNormal = GetWorldNormal(modelMatrix); ret.oWorldPos = float4(worldPos, GetDepth(ret.oPosition)); #if defined(D3D11) && defined(CLIPPLANE) ret.oClip = dot(ret.oPosition, cClipPlane); #endif #if defined(TOON_GS_OUTLINE) // white is default ret.oColor = float4(1,1,1,1); #endif #if defined(TOON_HAIR_RING) || defined(TOON_HAIR_RING_UV2) #if defined(TOON_HAIR_RING_UV2) ret.oHairUV = float2(((mul((float3x3)cViewInv, ret.oNormal) + 1) * 0.5).x, iHairUV.y); #else ret.oHairUV = float2(((mul((float3x3)cViewInv, ret.oNormal) + 1) * 0.5).x, iTexCoord.y); #endif #endif #ifdef TOON_MATCAP ret.oMatCapUV = ((mul((float3x3)cViewInv, ret.oNormal) + 1) * 0.5).xy; #endif #ifdef NORMALMAP float4 tangent = GetWorldTangent(modelMatrix); float3 bitangent = cross(tangent.xyz, ret.oNormal) * tangent.w; ret.oTexCoord = float4(GetTexCoord(iTexCoord), bitangent.xy); ret.oTangent = float4(tangent.xyz, bitangent.z); #else ret.oTexCoord = GetTexCoord(iTexCoord); #endif #ifdef TOON_GS_OUTLINE // outline offset is computed here so that the GS can almost pass-through instead of repeating clip-space transformation #ifdef TOON_CONTROL_MAP float outlinePower = Sample2D(ControlMap, ret.oTexCoord.xy).r * cOutlinePower.y; #else float outlinePower = cOutlinePower.x; #endif ret.oOutlineOffset = GetClipPos(worldPos + ret.oNormal * -(1+outlinePower) + normalize(cCameraPos - iPos) * cOutlinePower.y); #endif #ifdef PERPIXEL // Per-pixel forward lighting float4 projWorldPos = float4(worldPos.xyz, 1.0); #ifdef SHADOW // Shadow projection: transform from world space to shadow space GetShadowPos(projWorldPos, ret.oNormal, ret.oShadowPos); #endif #ifdef SPOTLIGHT // Spotlight projection: transform from world space to projector texture coordinates ret.oSpotPos = mul(projWorldPos, cLightMatrices[0]); #endif #ifdef POINTLIGHT ret.oCubeMaskVec = mul(worldPos - cLightPos.xyz, (float3x3)cLightMatrices[0]); #endif #else ret.oVertexLight = GetAmbient(GetZonePos(worldPos)); #endif return ret; } #endif #if defined(COMPILEGS) [maxvertexcount(6)] void GS(triangle in VSOutput vertices[3], inout TriangleStream<VSOutput> triStream) { VSOutput v1 = vertices[0], v2 = vertices[1], v3 = vertices[2]; triStream.Append(vertices[0]); triStream.Append(vertices[1]); triStream.Append(vertices[2]); // emit the flipped vertices triStream.RestartStrip(); // color all vertices black v1.oColor = v2.oColor = v3.oColor = float4(0,0,0,1); v1.oPosition = v1.oOutlineOffset; v2.oPosition = v2.oOutlineOffset; v3.oPosition = v3.oOutlineOffset; triStream.Append(v3); triStream.Append(v1); triStream.Append(v2); } #endif #if defined(COMPILEPS) // Fresnel-like, just with a fixed specular of 0,0,0 float3 GetRimlight(in float VdotH) { return pow(1.0 - VdotH, 5.0); } void PS(in VSOutput vtxIn, out float4 oColor : OUTCOLOR0) { float4 diffInput = Sample2D(DiffMap, vtxIn.oTexCoord.xy); #ifdef ALPHAMASK if (diffInput.a < 0.5) discard; #endif float4 diffColor = cMatDiffColor * diffInput; // Get material specular albedo #ifdef SPECMAP float3 specColor = cMatSpecColor.rgb * Sample2D(SpecMap, iTexCoord.xy).rgb; #else float3 specColor = cMatSpecColor.rgb; #endif // Get normal #ifdef NORMALMAP float3x3 tbn = float3x3(vtxIn.oTangent.xyz, float3(vtxIn.oTexCoord.zw, vtxIn.oTangent.w), vtxIn.oNormal); float3 normal = normalize(mul(DecodeNormal(Sample2D(NormalMap, vtxIn.oTexCoord.xy)), tbn)); #else float3 normal = normalize(vtxIn.oNormal); #endif // Get fog factor #ifdef HEIGHTFOG float fogFactor = GetHeightFogFactor(vtxIn.oWorldPos.w, vtxIn.oWorldPos.y); #else float fogFactor = GetFogFactor(vtxIn.oWorldPos.w); #endif float3 cameraDir = cCameraPosPS - vtxIn.oWorldPos.xyz; #if defined(PERPIXEL) // Per-pixel forward lighting float3 lightDir; float3 lightColor; float3 finalColor; float diff = saturate(GetDiffuse(normal, vtxIn.oWorldPos, lightDir)); #ifdef SHADOW diff *= GetShadow(vtxIn.oShadowPos, vtxIn.oWorldPos.w); #endif diff = Sample2D(ToneMap, float2(saturate(diff), 0)).r; #ifdef TOON_CONTROL_MAP float4 controlValues = Sample2D(ControlMap, vtxIn.oTexCoord); diff = max(controlValues.r, min(controlValues.g, diff)); #endif #ifdef TOON_VERTICAL_RAMP diff = min(diff, Sample2D(VerticalRamp, float2(vtxIn.oRampCoord, 0))); #endif #if defined(SPOTLIGHT) lightColor = vtxIn.oSpotPos.w > 0.0 ? Sample2DProj(LightSpotMap, vtxIn.oSpotPos).rgb * cLightColor.rgb : 0.0; #elif defined(CUBEMASK) lightColor = SampleCube(LightCubeMap, vtxIn.oCubeMaskVec).rgb * cLightColor.rgb; #else lightColor = cLightColor.rgb; #endif #ifdef SPECULAR float spec = GetSpecular(normal, cameraDir, lightDir, cMatSpecColor.a); finalColor = diff * lightColor * (diffColor.rgb + spec * specColor * cLightColor.a) * 2; #else finalColor = diff * lightColor * diffColor.rgb; #endif #ifdef AMBIENT finalColor += cAmbientColor.rgb * diffColor.rgb; finalColor += cMatEmissiveColor; oColor = float4(GetFog(finalColor, fogFactor), diffColor.a); #else oColor = float4(GetLitFog(finalColor, fogFactor), diffColor.a); #endif #else // Ambient & per-vertex lighting float3 finalColor = vtxIn.oVertexLight * diffColor.rgb; #ifdef EMISSIVEMAP finalColor += cMatEmissiveColor * Sample2D(EmissiveMap, vtxIn.oTexCoord.xy).rgb; #else finalColor += cMatEmissiveColor; #endif #ifdef TOON_MATCAP finalColor.rgb += EvaluateMatCap(vtxIn.oMatCapUV, vtxIn.oTexCoord); #endif #ifdef TOON_RIMLIGHT const float3 toCamera = normalize(cCameraPosPS - vtxIn.oWorldPos.xyz); const float3 Hn = normalize(toCamera - vtxIn.oNormal); const float vdh = clamp((dot(toCamera, Hn)), 0.0001, 1.0); finalColor.rgb += GetRimlight(dot(toCamera, vtxIn.oNormal)) * cRimLightColor.rgb; #endif oColor = float4(GetFog(finalColor, fogFactor), diffColor.a); #endif // linework always darkens #if defined(TOON_LINE_MAP) || defined(TOON_LINE_MAP_CONTROLLED) oColor.rgb *= SampleMSDF(vtxIn.oTexCoord, vtxIn.oNormal, normalize(cameraDir)); #endif #if defined(TOON_HAIR_RING) || defined(TOON_HAIR_RING_UV2) float4 hairRing = EvaluateHairRing(vtxIn.oNormal, vtxIn.oHairUV, vtxIn.oWorldPos); oColor.rgb = lerp(oColor.rgb, hairRing.rgb, hairRing.a); #endif #ifdef TOON_GS_OUTLINE oColor *= vtxIn.oColor; #endif } #endif This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,45 @@ #ifndef D3D11 sampler2D sControlMap : register(s3); sampler2D sMatCap : register(s4); sampler2D sHairRing : register(s4); sampler2D sLinework : register(s5); sampler2D sVerticalRamp : register(s6); sampler2D sToneMap : register(s7); #else Texture2D tControlMap : register(t3); Texture2D tMatCap : register(t4); Texture2D tHairRing : register(t4); Texture2D tLinework : register(t5); Texture2D tVerticalRamp : register(t6); Texture2D tToneMap : register(t7); SamplerState sControlMap : register(s3); SamplerState sMatCap : register(s4); SamplerState sHairRing : register(s4); SamplerState sLinework : register(s5); SamplerState sVerticalRamp : register(s6); SamplerState sToneMap : register(s7); #endif #if defined(COMPILEVS) || defined(COMPILEHS) || defined(COMPILEDS) cbuffer CustomVS : register(b6) { float4 cOutlinePower; #if defined(TOON_TESS) float4 cTessParams; #endif #if defined(TOON_VERTICAL_RAMP) float4 cVerticalRampShift; #endif } #endif #if defined(COMPILEPS) cbuffer CustomPS : register(b6) { float4 cRimLightColor; float4 cLineWorkPower; float4 cOutlinePower; float4 cHairRingColor; } #endif This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,41 @@ #include "Uniforms.hlsl" #include "Samplers.hlsl" #include "Transform.hlsl" #include "Toon_Data.hlsl" void VS(float4 iPos : POSITION, #ifndef NOUV float2 iTexCoord : TEXCOORD0, #endif float3 iNormal : NORMAL, #ifdef SKINNED float4 iBlendWeights : BLENDWEIGHT, int4 iBlendIndices : BLENDINDICES, #endif #ifdef INSTANCED float4x3 iModelInstance : TEXCOORD4, #endif out float4 oPos : OUTPOSITION) { // Define a 0,0 UV coord if not expected from the vertex data #ifdef NOUV float2 iTexCoord = float2(0.0, 0.0); #endif float4x3 modelMatrix = iModelMatrix; float3 worldPos = GetWorldPos(modelMatrix); #ifdef TOON_CONTROL_MAP float outlinePower = Sample2D(sControlMap, iTexCoord.xy).r * cOutlinePower.x; #else float outlinePower = cOutlinePower.x; #endif oPos = GetClipPos(worldPos + iNormal * -(1+outlinePower) + normalize(cCameraPos - iPos) * cOutlinePower.y); } void PS(out float4 oColor : OUTCOLOR0) { oColor = float4(0, 0, 0, 1); } This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,44 @@ #ifdef COMPILEPS float4 EvaluateHairRing(float3 normal, float2 secondaryUV, float3 worldPos) { return Sample2D(HairRing, float2(secondaryUV.x, secondaryUV.y)) * cHairRingColor; } // User: szamq // https://discourse.urho3d.io/t/matcap-shader-and-modelview-matrix/272/5 float3 EvaluateMatCap(float2 matcapUV, float2 uv) { float4 sampleValue = Sample2D(MatCap, matcapUV); float matcapPower = clamp(cAmbientColor.a, 0, 1); #ifdef TOON_CONTROL_MAP matcapPower *= Sample2D(ControlMap, uv).a; #endif return cMatEnvMapColor.rgb * sampleValue.rgb * matcapPower; } float MSDF(float r, float g, float b) { return max(min(r, g), min(max(r, g), b)); } float SampleMSDF(float2 uvCoord, float3 normal, float3 cameraDir) { float2 texSize = float2(0, 0); tLinework.GetDimensions(texSize.x, texSize.y); float2 stepSize = float2(4.0,4.0) / texSize; float4 samp = Sample2D(Linework, uvCoord); #ifdef TOON_LINE_MAP_CONTROLLED if (samp.a > saturate(dot(normal, cameraDir))) return 1.0; #endif float fldVal = (MSDF(samp.r, samp.g, samp.b) - 0.5) * dot(stepSize, 0.5 / fwidth(uvCoord)); float weight = saturate(fldVal + 0.5); // use step instead? might it alias too much if stepSize isn't good? return lerp(1.0, 0.0, weight); } #endif