One of the more disappointing things about UE4 is a lack of specularity on translucent objects, which makes stuff like my water more difficult. Some people have taken to passing in a light vector and building blinn/phong lighting in the pixel shader. I’ll be showing how to do that with the specular model that is closer to what UE4 uses, as well as another model for comparison.To start out here is the default lighting with no direct specular:Lit Translucency – The highlights are all due to screenspace reflection.
and here is UE4’s opaque shader:
In order to get a specular highlight i needed a light vector, so I made a level blueprint that passes the directional light vector to a material collection. This way multiple shaders can access the same global attribute if needed.
We can then use this new attribute to add a specular highlight. Here it is with a custom Blinn specular added through the emissive channel
Lit Translucency + Blinn
If you compare this to UE4’s specularity though, itsHere are each shader again, specular only (well…specular + screenspace reflections):
Blinn
UE4 Specular
UE4’s specular is much more advanced than Blinn and to get this kind of specularity, we will need to rebuild it from scratch like we did for the Blinn. This paper is a good read for those looking to figure out how exactly the shader works (WARNING: MATH). To get a handle on doing this in UE4, i used the Cook Toorance shader I wrote a while back as the basis for my rebuilt lighting. It uses Beckmann’s model of specularity, which is slightly different than the GGX approach UE4 uses.
Beckmann Specular –
While the specular is a lot more focused and apparent with this, it does not have as much spread as UE4’s which I was a little disappointed with. This definitely helps in the background, in terms of enhancing the wave’s readability in the distance. So instead i attempted to implement UE4’s specular model – GGX/Trowbridge-Reitz.GGX/Trowbridge-Reitz Custom –
Here is my the shader with UE4’s Lit translucency and GGX specular added.
Here are the different material functions for each. Let me know if you need anything clarified.EDIT: GGX originally had some issues but is now MORE BETTER and updated graphs have been uploaded. Thanks for the help Eric!
12/11/2014 01:52:41 am
You are a saint for tackling great (and under-documented) topics like these.
This might be jumping ahead, but do you have any plans to account for specular occlusion/shadowing? In this thread: https://forums.unrealengine.com/showthread.php?2127-Light-Vector-Any-Alternative-Methods …. Epic Dev RyanB mentions “recreating shadow information using vertex shaders”… Vague, but intriguing.
Can’t wait to see more. and thanks again for your investigations!
12/11/2014 02:30:56 pm
Glad you like it! I cut my teeth on shaders by picking apart UDK shaders back in the day, so i’m hoping others do the same with my work.
In terms of shadowing, i hadn’t really considered much beyond an occlusion texture (lightmaps perhaps). My initial thoughts are it should be possible to spit out the shadow data into a render texture, which you could then use. It might not be terrible for performance if you only re-rendered when the light moved.
Doing any of that in the vertex shader is a whole new level of dark art to me though. I’ll keep it in mind as i dig into other things though, since that’s usually how these types of things come to me.
Thanks again for the awesome breakdowns. I do have a few questions:
After stepping through your example functions, both Blinn and Beckmann are working just fine, but I seem to be getting crazy edge artifacts with the GGX solution… Here’s what it’s ended up looking like:
https://dl.dropboxusercontent.com/u/3405058/UDN/GGX_issue.png
Not sure from your screenshots if you’re getting similar issues, or if I botched something. Just in case, here’s the material network I constructed based on your layout: (text file for copy/paste in UE4 Material function):
https://dl.dropboxusercontent.com/u/3405058/UDN/GGX_issue.txt
One of the whitepapers linked-to in all of this had a sample of HLSL code for a GGX specular (here: https://dl.dropboxusercontent.com/u/3405058/UDN/GGX_Beckmann_Spec_code.txt)… which I implemented in Unreal, and it seems to be giving me better results (at least without the horizon artifact)… although the falloff seems significantly more aggressive… here’s the material nodes setup if you’re interested: https://dl.dropboxusercontent.com/u/3405058/UDN/GGX_revised.txt
As always, thanks for the insight. Prior to reading your post, I felt like I had zero idea on how to manually implement my own specularity… and now I feel like it’s much more approachable. Still stumped on the specular occlusion issue tho… hmm.
12/15/2014 08:23:22 pm
Nice find! Yeah i noticed the same thing. Its tied to the frensel somehow – the frensel artifacting i mentioned. Unfortunately my comp is down right now so i can’t compare, but its probably just a node or 2 missing/incorrectly wired. Once i figure out the source it should be a quick fix to that section. I was kinda hoping that by putting it out there we could find a more perfect solution, and it sounds like what you found is the first step towards that. It can be frustrating, but seeing a hypothesis go wrong at least tells me i’m pushing my limits 🙂
Cool, hopefully that code proves useful. I look forward to seeing your findings, and I’ll ping you back if I come across any potential fixes/improvements. Good luck with the PC being down… No fun!
Good news: I’m back in business! Took a look at your graph and the code – Thanks again BTW. It was just what i needed to make the fixes i needed. Here is the amended graph – still not perfect but a good improvement:
https://www.dropbox.com/s/ptczu0zs556geen/GGX_V2.txt?dl=0
I noticed a few minor differences in your graph to my results, and that may be causing some issues. Check your visibility term against mine and the code. I think we were both getting confused by order of operations type issues…easy to miss when reading a line of code!
Also little under the hood optimization tip – its usually faster on the GPU to stack multiply nodes if you know you will be powering by a static integer rather than using a power node. Basically it lets the shader compiler skip a bunch of fancy math to deal with powering by float values. At least that’s what a few smart people have told me.
I did notice some frensel wierdness still, at least in the preview window. Just has a wierd effect at glancing angles. Perhaps i inversed something? Either way its getting closer
Hey, thanks for pointing out my math error! You were totally correct, I had an add before a multiply. Bad math.
Oddly enough, I also noticed an issue with the “correct” Visibility Term formula that seems to be adding artifacts around the “horizon” of the highlight… I’m noticing it on your latest version of the shader as well (easy to see on a smooth sphere with no normalmaps). The hacky workaround I am using, which seems to help, is this…
Instead of this:
1.0f / (nDotX + sqrt(m2 + (1 – m2) * nDotX * nDotX));
Try this:
1.0f / (0.2 + sqrt(m2 + (1 – m2) * nDotX * nDotX));
Replacing that gets rid of the horizon artifact, with no perceptible loss of quality. Here’s a comparison of both of our shaders, with and without the hack added: https://dl.dropboxusercontent.com/u/3405058/UDN/GGX_issue2.png
Your implementation looks like it has a slightly larger spread on the highlight, which I like a bit more.
Anyway, I realize that this goofy hack isn’t really “accurate” to the spirit of the problem, but hey… it seems to work. And I can’t question the results… so I might just use that one, for now.
Thanks also about the tip of stacking multiplies instead of using a power… I had no idea that was more optimal!
Have a great week!
That’s great! After looking at your stuff and my nodes, I realized i could just bias NDotX by 0.01 or something small and eliminate the artifact. I think taking the square root of something too close to zero was causing it, so by making sure it has a higher minimum we can fix that.
Here is the graph im rolling with. Just basically adds a Max( (NDotX*NDotX) , 0.01) instead of straight (NDotX*NDotX):
https://www.dropbox.com/s/czkj82dbk0pv8a9/GGX_fixed.txt?dl=0
The spread is real nice and behaves like i would expect. I’m not sure what UE4 uses for specular albedo but it looks close to zero. In any case i’m no longer seeing any glaring issues so i’m calling this one “finished”… 😛
Cheers
Hey, good fix. That certainly seems more reasonable than my hacky *0.2 workaround. All in all, I’m super happy with the results and will be calling it “done” as well!
At some future time, I’m sure I’ll try exploring solutions for specular shadowing/occlusion, and if I make any headway, I’ll be sure and post it here. Thanks again for all your experimentation!
Have a great New Years!!!
-eaa
Thank you very much !
No problem. Hopefully i’ll get to post some new stuff soon. I’ve been really busy recently
Incredibly valuable resource. Thank you sir!