[UE4] Adding a custom shading model
转自:https://blog.felixkate.net/2016/05/22/adding-a-custom-shading-model-1/

This was written in February 2016 which means it was written for an old version of the Unreal Engine.
There were a few changes with the newer versions so this is not up to date (mostly changes in which lines the code can be found)
In the comment section of the second part someone mentioned a few useful hints for changes to the 4.14 version so be sure to give that a look if something isn’t working as expected.
For my first three posts I want to write about how to add a custom shading model for the Material-Editor of the Unreal Engine 4. This is something that isn’t very well documented and quite a few people on the forums were struggling with it so I would like to share my results so far.

Rows: 1. roughness & specular 2. shadow smoothness 3. light support 4. normal map examples
First of here’s a few pictures on what the result looks like. This model will give us some control over the shadows from within the Material-Editor to achieve a toony look. It also offers support for metallic, translucency etc. Some of the effects like the outline, the fresnel or the colored shadow are handled in the Material-Editor.
If you feel interested in how this was implemented please continue reading. Even if you try to implement something else or simply want to change the BRDF you can continue reading as the groundwork is basically the same.
Before we get to the implementation there’s a few things worth of notice:
- You should be familiar with at least one shading language. Unreals shading files (.usf) are pretty similar to HLSL.
- This is just a guide about doing some changes to the deferred pipeline so rendering on mobile devices might not be affected (I haven’t made any changes to the forward lighting). I don’t plan to look into that at the moment but if you have any information on the subject you can add it in the comments.
- I won’t upload the whole engine code so don’t ask for it.
- This shading model is mostly based on the information found in an old Thread on the Unreal Forums. I also found out later that there was another attempt making a custom toon shading model which is based on the preview version so you might look up line numbers there if you have problems finding something.
Let’s get started.
First we need to get our hands on a source code version of Unreal4 since the normal launcher installation won’t let us modify the code. Either grab a fresh source copy of the Engine from Epics GitHub or if you already have one make a backup of all files you are editing and then open the solution in the editor of your choice. I used version 4.10 of the Engine. If you are using a really old version you will notice differences in the base structure of the code so this probably won’t be compatible. On the newer versions (current preview builds) Unreal added a few more shading models and the code has changed a bit so keep that in mind (mostly line changes and new names for the custom variables).
EngineTypes.h
First we need to add our new shading model so that the engine actually knows about it. Open up EngineTypes.h and scroll to line 203. You should find the enum EMaterialShadingModel. Before MSM_Max add a line:
MSM_StylizedShadow UMETA(DisplayName = "Stylized Shadow"),
This will add a new model to our dropdown inside the material editor. You can of course name it however you want just remember the name of the enum.
MaterialShared.cpp
Next we need to tell the shader about our newly added model. At line 1257 add a line below the last shading model.
case MSM_StylizedShadow: OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_STYLIZED_SHADOW"), TEXT("")); break;
Material.cpp
The last thing we want to do is enable a few pins for additional customizing inside of our shader. Scroll down to around line 490. This part of the code defines which pins will be unlocked for the different shading models. Instead of going through the process of creating new pins we will hijack some of the existing ones which aren’t in use. Search for the cases MP_Clearcoat and MP_ClearcoatRoughness and simply change the line below to this:
Active = ShadingModel == MSM_ClearCoat || ShadingModel == MSM_StylizedShadow;
In the Preview Versions those variables should be called something like MP_Custom. This is because they get reused a for different shading models. I decided against adding a new input here as I don’t think it’s good practice to sent more information to the graphics card as necessary. If you want to add another input instead refer to the code changes made by the other toon shading model mentioned above.

Our new shading model
And this should be all. If you would compile now the Material-Editor should show our newly added shading model inside the dropdown menu.
This is the second part of the shading model implementation. In this part we are going to edit a few shader files. Unreal shader files end with a .usf but if you are familiar with hlsl you shouldn’t have any problems. You should also at least understand what deferred rendering means. If you are unfamiliar with that subject take a look at Wikipedia. As this part is mostly about coding there won’t be many pictures. Sorry about that. :<
Alright time to move on.
Look for the Shaders folder directly nested inside the project root of the engine. Editing shader files can be done in any text editor since the compiling will happen once you open the Unreal Editor. On Windows using VisualStudio is a good choice since the compiler tells you were the parser found an error and sometimes even information about what it encountered.
BasePassPixelShader.usf.
Let’s start were our input gets written into the GBuffer. For this open up BasePassPixelShader.usf.
This shader writes all the values acquired from pins inside the Material-Editor into the GBuffer. Keep in mind that this shader will render the Geometry itself.
First let’s take a look at how the GBuffer is build. For now we only need to worry about GBuffer A – D.
- GBuffer A – float3: normals, float: packed alpha
- GBuffer B – float: metallic, specular, roughness, decalMask
- GBuffer C – float3: baseColor, float: ao
- GBuffer D – float: opacity, float3: customData
CustomData in GBuffer D has different uses depending on the shading model.
We will also make use of it later.
You might have noticed that the emissive isn’t listed inside one of the buffers. Unreal calculates lighting by starting from zero and then adding the light.
The emissive value gets written into the basic output instead of a buffer so it will already be there before the light is added.
First go to line 774. Our target is directly under the comment saying “Volume lighting for lit translucency”.
Change the next line to:
#if (MATERIAL_SHADINGMODEL_DEFAULT_LIT || MATERIAL_SHADINGMODEL_SUBSURFACE || MATERIAL_SHADINGMODEL_STYLIZED_SHADOW) && (MATERIALBLENDING_TRANSLUCENT || MATERIALBLENDING_ADDITIVE)
We need to do this so that we actually see something if our blending is set to translucent or additive.
After this jump over to line 879. This is were we specify shading model specific data. Let’s add our own model here:
#elif MATERIAL_SHADINGMODEL_STYLIZED_SHADOW
GBuffer.ShadingModelID = SHADINGMODELID_STYLIZED_SHADOW; float smoothness = GetMaterialClearCoat(MaterialParameters); GBuffer.CustomData.x = smoothness;
We use the CustomData to add our hijacked pins into the GBuffer. We still have the GetMaterialClearCoatRoughness() pin unlocked so if you need another custom input write that to CustomData.y. This is also were you might do changes to all the other GBuffers like baseColor etc. if your shader requires it.
Again in the preview version of the engine the GetMaterialClearCoat() should be called something different like GetMaterialCustomData0().
And that’s all for this shader. Time to move on.
MaterialTemplate.usf
The next step is optional so skip it if you want. What we do here is changing the color with which our shading model is displayed in the Shading Model Buffer Visualization. Go to line 1010 and you will find a list of colors used for the different shading models. Add this to line 1022:
else if (GBuffer.ShadingModelID == SHADINGMODELID_STYLIZED_SHADOW) return float3(0.4f, 0.0f, 0.8f); // Purple
and this to line 1034:
case SHADINGMODELID_STYLIZED_SHADOW: return float3(0.4f, 0.0f, 0.8f); // Purple
DeferredShadingCommon.usf
Next up is setting the definition for our shading model. In the shader at line 227 add this and replace the line saying max number with our new maximum.
#define SHADINGMODELID_STYLIZED_SHADOW 7
#define SHADINGMODELID_NUM 8
ShadingModels.usf
Now that we have our basic setup out of the way let’s get to the interesting part. This file contains information about the shading itself. Take a look at the already defined ones and you will get an idea what’s going on here. The BRDF’s are part of a different file called BRDF.usf so if you want to add a new function for a BRDF you can do that there. If you are unfamiliar with BRDF’s it’s basically how light information get’s handled in this case mainly specular and fresnel. Unreal has a few unused snippets there as well so you could try out replacing the lambert model with the oren nayar one in one of the shading functions in this file etc.
For this shading model I haven’t added anything BRDF related. Instead I made a copy of the SimpleShading() function and made changes to that. The reason behind it is that we only need the specular calculation. In line 69 after the StandardShading() function we add our own:
// Stylized shadow shading model for manipulating the gradient of the specular
float3 StylizedShadowShading( FGBufferData GBuffer, float Roughness, float3 L, float3 V, half3 N, float2 DiffSpecMask )
{
float range = GBuffer.CustomData.x * 0.5; float3 H = normalize(V + L);
float NoH = saturate( dot(N, H) ); return GBuffer.DiffuseColor + saturate(smoothstep(0.5 - range, 0.5 + range, D_GGX( Roughness, NoH )) * GBuffer.SpecularColor);
}
This is a very basic shading model that takes the base color and adds the specular on top of it. For this we use the already in BRDF.usf defined D_GGX() function. Here we will use our CustomData for the first time to manipulate how sharp the edges of the specular highlight will be. Again if you are unfamiliar with the smoothstep() function take a look here. A smoothstep of (0, 1, x) would give us the default while one at (0.5, 0.5, x) would be the same as a round(). Going up or down on both sides would shift the resulting gradient in that direction.
After we defined our function we need to tell the shader to actually use it. For this move to line 270 and add a case for our shading model inside the SurfaceShading() function.
case SHADINGMODELID_STYLIZED_SHADOW:
return StylizedShadowShading( GBuffer, LobeRoughness.y, L, V, N, DiffSpecMask);
We now have added our own custom shading. Let’s get to the last part of this guide: The lighting function. If you just wanted to make changes to the BRDF you can stop now. The next section is only for getting the toony shadow look.
DeferredLightingCommon.usf
The actual lighting for the different light types takes place in DeferredLightPixelShaders.usf but as all the lights use the functions defined here we only need to change this one. We will only make changes to a single big function here with the name GetDynamicLighting(). This one calculates our light and shadow in the deferred rendering pipeline. So what do we do here? It’s simple: We want to remove the NdotL calculation from our models shadows and sharpen the edges (after the engine went through all this work of smoothing them :>).
In line 352 we add a new branch.
float3 Attenuation = ; // In case we want to stylize the shadow changing it's falloff
BRANCH
if (ShadingModelID == SHADINGMODELID_STYLIZED_SHADOW)
{
float range = ScreenSpaceData.GBuffer.CustomData.x * 0.5;
Attenuation = LightColor * ((DistanceAttenuation * LightRadiusMask * SpotFalloff) * smoothstep(0.5 - range, 0.5 + range, SurfaceShadow) * 0.1);
}
else
{
Attenuation = LightColor * (NoL * SurfaceAttenuation);
}
we also need to need to make changes to the default calculation to make use of our newly added attenuation variable so in lines 372 and 376 change
LightColor * (NoL * SurfaceAttenuation)
to
Attenuation
That’s it. We have everything together for our custom shading model. In case you haven’t done it yet recompile the changed C++ classes and start the engine.
The startup will take some time now as it needs to recompile the shaders. Once all shaders are compiled and your project is opened create a new Material in the Content-Browser and change it’s shading model to our newly created one. Add in a few constants and it should look like this.

The last and final part will be about adding stuff to the Material and making a Post-Process for a colored outline.

Material instance of the material we are going to make
For the final part of this guide I will write about the few things I added to the material and my approach to do outlines which take in account the base color.
This part is optional you already have everything you need.
If you are still interested continue reading this part will have more pictures in it. :>

The material
This is the whole basic material. There is not much stuff in it yet and it’s up to you to extend it. On the left side we can see the color parameter or the base texture. Let’s component mask it so we don’t include our alpha in further calculations. The alpha goes directly into opacity / opacity mask. Even if the output pin is deactivated we can still make use of it later by overwriting the blending model inside of the material instance.
For the normal input either plug in a normal map or leave it at default (0, 0, 1). You could also do static switches for baseTexture and normalMap that switch between color input and texture input.
Create parameters for Specular, Metallic, Roughness and Smoothness and plug them into their pins. Smoothness goes into the ClearCoat pin.

Adding the fresnel
The fresnel gets connected as above. For it’s values I used exponent: 2.0 and base reflect fraction: 0.0. The smoothstep node doesn’t exist so we have to create it ourselves. Add a custom node and set it’s output type to float1. Add two inputs. Add the following code into it:
- return smoothstep(0.5 - Threshold, 0.5 + Threshold, IN);
The multiply after this takes the base color / texture we masked as second input. Also add another scalar parameter called FresnelIntensity so that we can turn down it’s value.

The shadow coloring
For the colored shadow add a scalar parameter called ColoredShadow. This will control how much color will be added to the emissive. Keep in mind the closer rgb gets to 1 the smaller the effect of the power node will be. (Multiplying 1 x 1 = 1) Play around with the exp value of the power node. For me a value of 2.5 looked best for characters having light skin tone textures.
Lastly add the results of fresnel and colored shadow together and plug them into the emissive input. As you might have guessed this will make not only the shadow brighter but also the base texture but for now I don’t know of a better way to colorize shadows except doing that in a post process by somehow faking the calculated light.

Outline post process overview
Now for the colored outlines. This is basically a modified version of the outline highlighter post process that Epic used in the example content.

Lower left of the material
This part is for creating the offset of the outline. If you ever did any sobel outline or bluring or something similar you should be familiar with the concept. By comparing the basic renderTexture to offsetted versions of it we can find out the difference between neighbouring pixels.

Lower right of the material
Here we will do exactly that to compare the scene depth. We use the scene depth instead of the custom depth so that our outlines are not rendered in front of other geometry.

Upper half of the material
This part is quite a mess. We offset the base color and the custom depth. The custom depth is used as a mask for the color so that we can separate the objects that should have the outline from the ones that shouldn’t. I decided to combine the colors by using the max node so that the brighter color will prevail. Once we have the offsetted colored silhouette of the base color we do some calculations to make it darker. We also combine and clamp all the custom depths to make a mask of the colored area. This part is probably not optimal as it uses quite a lot of texture lookups but I don’t now another way around it so if someone has a suggestion feel free to post a comment. :>
The final part of the post process is simple: We multiply the lower half with the custom depth mask we just created and use this as alpha for a lerp between our outline color and a SceneTexture that renders the scene as it is (PostProcessInput0). At the end we plug in everything into our emissive pin.
[UE4] Adding a custom shading model的更多相关文章
- UnrealEngine4 PBR Shading Model 概述
虽然是概述,但内容并还是有些多,写上一篇PBR概念概述后,也在考虑怎么继续下去,最后还是觉得先多写一些东西再慢慢总结,所以还是尽量把这些年PBR相关的Paper精粹沉淀下来吧. 因为UE4 ...
- 基于Shading Model(对光照变化一定不变性)的运动目标检测算法
光照模型(Shading Model)在很多论文中得到了广泛的应用,如robust and illumination invariant change detection based on linea ...
- 【计算机视觉】基于Shading Model(对光照变化一定不变性)的运动目标检测算法
光照模型(Shading Model)在很多论文中得到了广泛的应用,如robust and illumination invariant change detection based on linea ...
- Adding a custom jar as a maven dependency
Using maven in a Java project is great. It manages builds (as customized as you may need), execution ...
- adding validation annotators to model classes 在linq to EntityFrame的Model中添加前台验证validation annotators
The same solution can be applied for LINQ to SQL. The snippet the article shows for using the Metada ...
- Physically Based Shader Development for Unity 2017 Develop Custom Lighting Systems (Claudia Doppioslash 著)
http://www.doppioslash.com/ https://github.com/Apress/physically-based-shader-dev-for-unity-2017 Par ...
- Django Model field reference
===================== Model field reference ===================== .. module:: django.db.models.field ...
- 初探UE4中的Profiling【转】
http://blog.ch-wind.com/ue4-profiling-preview/ Profililng是成品制作过程中非常重要的一个步骤,通过Profiling才能提高运行效率使得作品达到 ...
- UE4材质初探
转自:http://www.unrealchina.net/portal.php?mod=view&aid=233 UE4的材质表面上看起来很简单,可是到了用的时候却总是没有办法实现好的效果. ...
随机推荐
- LoadRunner【第一篇】下载、安装、破解、修改端口号
loadrunner11下载 loadrunner11大小有4g多,相对另外一款开源的性能测试工具jmeter来说,是非常笨重的了,可以关注公众号联系作则获取. loadrunner11安装(win7 ...
- 如何保护你的 Python 代码 (一)—— 现有加密方案
https://zhuanlan.zhihu.com/p/54296517 0 前言 去年11月在PyCon China 2018 杭州站分享了 Python 源码加密,讲述了如何通过修改 Pytho ...
- base64图片编码大小与原图文件大小之间的联系
base64图片编码大小与原图文件大小之间的联系 有时候我们需要把canvas画布的图画转换成图片输出页面,而用canvas生成的图片就是base64编码的,它是由数字.字母等一大串的字符组成的,但是 ...
- 监听localStorage中的数据变化
问题描述:我们在js里面获取了某一个localstorage的值,但是后期它可能改变了,我们js只执行一遍没办法再次获取它的值,当然可以刷新页面获取,但如果是我们的但页面就不能刷新页面了,此时:我们可 ...
- Let Start
A free timing software with very small memory occupation. This tool is a pure green convenient off ...
- JavaScript基础10——正则
什么是正则? 正则表达式(regular expression)是一个描述字符规则的对象.可以用来检查一个字符串是否含有某个字符,将匹配的字符做替换或者从某个字符串中取出某个条件的子串等. ...
- linux学习14 Linux运维高级系统应用-glob通配及IO重定向
一.回顾 1.bash基础特性:命令补全,路径补全,命令引用 2.文件或目录的复制,移动及删除操作 3.变量:变量类型 存储格式,数据表示范围,参与运算 二.bash的基础特性 1.globbing: ...
- CT107D电路解析
1.译码器: 其中,A.B.C为输入端, 对应关系如下: CBA 十进制 输出(低电平) 000 0 Y0 001 1 Y1 010 ...
- ent 基本使用 一 schema 迁移
ent 是 facebook 开源的golang orm 框架,简单强大,以下是一个简单使用 环境准备 安装ent 工具 go get github.com/facebookincubator/ent ...
- [RN] React Native 删除第三方开源组件依赖包 后 还要做的 (以 删除 react-native-video为例)
近期测试使用了下 react-native-video 使用一直不成功,后来想着删除掉, 使用命令: npm uninstall react-native-video 重新编译后,还是一直报错 后来 ...