你的显卡渲染器不支持弹幕阿尔法混合与浮点渲染目标(d3dfmt_a16b16g16r16f),这是需要运…

当前位置: &
求翻译:Your video card does not supprt alpha blending with floating point render targets (D3DFMT_A16B16G16R16F),Which is required to runthis game,Exiting...是什么意思?
Your video card does not supprt alpha blending with floating point render targets (D3DFMT_A16B16G16R16F),Which is required to runthis game,Exiting...
问题补充:
正在翻译,请等待...
您的显示卡没有混和与浮动小数点的supprt阿尔法回报目标(D3DFMT_A16B16G16R16F),需要对runthis比赛,退出…
您的显示卡没有混和与浮动小数点的supprt阿尔法回报目标 (D3DFMT_A16B16G16R16F),需要对runthis比赛,退出…
您的显卡不会不灶具 alpha 混合与浮动点呈现目标 (D3DFMT_A16B16G16R16F),这是需要到 runthis 游戏,Exiting......
你的视频卡不与浮点协调的 supprt alpha 致使目标 ( D3DFMT_A16B16G16R16F ),到 runthis 比赛需要,退出 ...
我来回答:
参考资料:
* 验证码:
登录后回答可以获得积分奖励,并可以查看和管理所有的回答。 |
我要翻译和提问
请输入您需要翻译的文本!努力加载中,稍等...
暂无新消息
努力加载中,稍等...
已无更多消息...
这些人最近关注了你
努力加载中,稍等...
已无更多消息
努力加载中,稍等...
已无更多消息
& Unity渲染教程(九):复杂材质
Rendering 9:Complex Materials
征集热心朋友翻译文章,奖励规则:每100汉字奖励10QB,30天内互动奖励0 - 50QB.
翻译请求:请求翻译
该文章来自用户转载
Create a custom shader GUI.Mix metals and nonmetals.Use nonuniform smoothness.Support emissive surfaces.This is the ninth part of a tutorialseries about rendering. , we added support forenvironmental maps. In this part we'll combine multiple textures to createcomplex materials. But before we get to that, we need a better GUI for ourshader.This tutorial was made with Unity5.4.1f1.Same shader, different maps.User InterfaceUp to this points, we've been using Unity's defaultmaterial inspector for our material. It is serviceable, but Unity's standardshader has quite a different look. Let's create a custom inspector for our ownshader, mimicking the standard shader.Our default inspector vs. the standardshader inspector. ShaderGUIWe can create a custom inspector by adding a class thatextends UnityEditor..As it is an editor class, place its script file in an Editor folder.usingUnityEusingUnityE publicclass MyLightingShaderGUI :
{} Don't we need to extend ?Unity 4.1 added support for custom material inspectors, viaextending .You can still do this, but
was added as an alternative in 5.0. Its creation has something to do withSubstance materials. Unity uses
for the standard shader, so we'll use it as well.Under the hood, Unity uses the default material editor forshaders that have a custom
associated with them. This editor instantiates the GUI and invokes its methods.To use a custom GUI, you have to add the CustomEditor directive to a shader, followed by a string containing the name ofthe GUI class to use.Shader "Custom/My First Lighting Shader" {
CustomEditor"MyLightingShaderGUI"} Can
classes be putinside namespaces?Yes. You have to specify the fully-qualified class name inthe shader.CustomEditor "MyNamespace.MyShaderGUI"To replace the default inspector, we haveto override the .OnGUI method. This method has two parameters. First, a reference to a .This object manages the inspector of the currently selected material. Second,an array containing that material's properties.public class MyLightingShaderGUI :
publicoverride void OnGUI (
editor, [] properties
}}Inside this method, we can create our own GUI. As we're notdoing so yet, the inspector has become empty.Creating a LabelThe standard shader GUI is split into two sections, one forthe main maps, and another for the secondary maps. We'll use the same layout inour GUI. To keep the code clean, we'll use separate methods for distinct partsof the GUI. We start with the main section and its label.public override void OnGUI (
editor, [] properties
voidDoMain() {
.Label("Main Maps");
}Main maps label.How does
work?The Unity Editor is created with Unity's immediate-mode UI.This is Unity's old UI system, which was also used for in-game UIs before thecurrent canvas-based system.The basis of the immediate-mode UI is the
class. It contains methods which create UI widgets. You have to use rectanglesto position each element explicitly. The
class provides the same functionality, while automatically positioning thewidgets using a simple layout system.Besides that, the
classes provide access to widgets and features for editor UIs.The standard shader has a bold label, so wewant a bold label as well. This is done by adding a GUI style to the label, inthis case .boldLabel..Label("Main Maps", .boldLabel);Bold label.Showing AlbedoTo show the properties of our material, we have to accessthem in our methods. We could pass the parameters of OnGUI on toall other methods, but this would lead to a lot of repeated code. Instead,let's put them in fields.
public override void OnGUI (
editor, [] properties
this.editor =
this.properties =
}Do we need to copy the references each time OnGUI is invoked? decides when a new
instance is created. This currently happens when a material is selected, as youmight expect. But it also happens when an undo or redo action is performed.This means that you cannot rely on a
instance sticking around. Each time, it could be a new object instance. Youcould think of OnGUI as if it were a static method, even though itisn't.The albedo map is shown first in thestandard shader. This is the main texture. Its property sits somewhere insidethe properties array. Its array index depends on the order in which theproperties are defined in our shader. But it is more robust to search for it byname.
contains the FindProperty method, which does exactly that, given a name and a property array.void DoMain () {
.Label("Main Maps", .boldLabel);
mainTex = FindProperty("_MainTex", properties);
}Besides the texture property, we also needto define the contents of a label. This is done with ,which is a simple container class. mainTex = FindProperty("_MainTex", properties);
albedoLabel = new ("Albedo"); But we've already named the main texture Albedo in our shader. Wecan just use that name, which we can access via the property. albedoLabel = new (mainTex.displayName);To create one of those small texturewidgets, we have to rely on the editor that we've been given a reference to. Ithas a collection of methods to draw such widgets. mainTex = FindProperty("_MainTex", properties);
albedoLabel = new (mainTex.displayName);
editor.TexturePropertySingleLine(albedoLabel, mainTex);Albedo map. This is beginning to look like thestandard shader! But that inspector also has tooltips, when you hover over theproperty labels. In the case of the albedo map, it says Albedo (RGB) andTransparency (A).We can add a tooltip as well, by simplyadding it to the label content. As we don't support transparency yet, let'sjust use Albedo (RGB). albedoLabel =
new (mainTex.displayName, "Albedo (RGB)");Albedo with tooltip. The TexturePropertySingleLine methodhas variants that work with more than one property, up to three. The firstshould be a texture, but the others can be something else. They will all be puton the same line. We can use this to display the tint next to the texture. tint = FindProperty("_Tint", properties);
editor.TexturePropertySingleLine(albedoLabel, mainTex, tint);Albedo map and tint. Let's skip ahead to the bottom of themain section. That's where the tiling and offset values of the main texture areshown. This is done with the .TextureScaleOffsetPropertymethod.editor.TexturePropertySingleLine(albedoLabel, mainTex, tint);
editor.TextureScaleOffsetProperty(mainTex); Convenience MethodsInstead of using the exiting FindProperty method, let's create one that only requires a name parameter, taking advantageof our properties field. This will make our code more legible. FindProperty (string name) {
returnFindProperty(name, properties);}Switch to using this method in DoMain.Also, we can directly pass the tint property to the TexturePropertySingleLine method. We're not using it anywhere else.void DoMain () {
.Label("Main Maps", .boldLabel);
mainTex = FindProperty("_MainTex");
albedoLabel =
new (mainTex.displayName, "Albedo (RGB)");
editor.TexturePropertySingleLine(
albedoLabel, mainTex, FindProperty("_Tint")
editor.TextureScaleOffsetProperty(mainTex);
} Let's also create a method to configure thecontents of a label. We only need to use a single static
instance for this. We'll just replace its text and its tooltip. As we might notneed a tooltip all the time, let's make it optional, with a default parametervalue.
static staticLabel = new ();
static MakeLabel (string text, string tooltip = null) {
staticLabel.text =
staticLabel.tooltip =
returnstaticL
} It's even more convenient if we don't haveto bother with extracting the display name from properties all the time. Socreate a MakeLabel variant that does this as well.
static MakeLabel (
property, string tooltip = null
staticLabel.text = property.displayN
staticLabel.tooltip =
returnstaticL
} Now DoMain can become evensmaller. The same goes for all our future methods.
void DoMain () {
.Label("Main Maps", .boldLabel);
mainTex = FindProperty("_MainTex");
editor.TexturePropertySingleLine(
MakeLabel(mainTex, "Albedo (RGB)"), mainTex, FindProperty("_Tint")
editor.TextureScaleOffsetProperty(mainTex);
} Showing NormalsThe next texture to be displayed is the normal map. Insteadof putting all the code in DoMain, delegate it to a separate DoNormals method. Invoke it after the albedo line, before the tiling and offset.
DoNormals();
editor.TextureScaleOffsetProperty(mainTex); The new DoNormals method simplyretrieves the map property and displays it. The standard shader doesn't provideany extra tooltip info, so we won't either.
voidDoNormals () {
map = FindProperty("_NormalMap");
editor.TexturePropertySingleLine(MakeLabel(map), map);
} Of course there is a bump scale as well, soadd it to the line.
editor.TexturePropertySingleLine(
MakeLabel(map), map, FindProperty("_BumpScale")
); Normal map and bump scale. The standard shader only shows the bumpscale when there is a normal map assigned to the material. We can do this too,by checking whether the property references a texture. If it does, show thebump scale. If not, just use null as an argument for TexturePropertySingleLine.
editor.TexturePropertySingleLine(
MakeLabel(map), map,
map.textureValue ? FindProperty("_BumpScale") : null
); Hidden bump scale. Showing Metallic and SmoothnessThe metallic and smoothness properties are simple floatranges. At least, for now. We can show them via the general-purpose .ShaderProperty method. Unlike the texture methods, this method has the property as its firstargument. The label contents come second.void DoMain () {
editor.TexturePropertySingleLine(
MakeLabel(mainTex, "Albedo (RGB)"), mainTex, FindProperty("_Tint")
DoMetallic();
DoSmoothness();
DoNormals();
editor.TextureScaleOffsetProperty(mainTex);
voidDoMetallic () {
slider = FindProperty("_Metallic");
editor.ShaderProperty(slider, MakeLabel(slider));
voidDoSmoothness () {
slider = FindProperty("_Smoothness");
editor.ShaderProperty(slider, MakeLabel(slider));
} Metallic and smoothness. We can make these properties line up with the other labels,by increasing the indent level of the editor. In this case, by two steps.The indent level can be adjusted via the static .indentLevel property. Make sure to reset it to its old value afterwards.void DoMetallic () {
slider = FindProperty("_Metallic");
.indentLevel += 2;
editor.ShaderProperty(slider, MakeLabel(slider));
.indentLevel -= 2;
void DoSmoothness () {
slider = FindProperty("_Smoothness");
.indentLevel += 2;
editor.ShaderProperty(slider, MakeLabel(slider));
.indentLevel -= 2;
} Indented properties. Showing the Secondary MapsThe secondary maps work just like the main maps. So createa DoSecondary method which takes care of the bold label, thedetail texture, and its tiling and offset.public override void OnGUI (
editor, [] properties
this.editor =
this.properties =
DoSecondary();
voidDoSecondary () {
.Label("Secondary Maps", .boldLabel);
detailTex = FindProperty("_DetailTex");
editor.TexturePropertySingleLine(
MakeLabel(detailTex, "Albedo (RGB) multiplied by 2"), detailTex
editor.TextureScaleOffsetProperty(detailTex);
} Adjust the display name of the detailtexture in our shader, to match the standard shader._DetailTex ("Detail Albedo", 2D) = "gray" {}Secondary maps. The detail normal map works just likethe main normal map. Curiously, the standard shader GUI doesn't hide the detailbump scale. But we're consistent, so we do hide it when there's no detailnormal map.void DoSecondary () {
DoSecondaryNormals();
editor.TextureScaleOffsetProperty(detailTex);
voidDoSecondaryNormals () {
map = FindProperty("_DetailNormalMap");
editor.TexturePropertySingleLine(
MakeLabel(map), map,
map.textureValue ? FindProperty("_DetailBumpScale") : null
} Complete inspector.
Mixing Metal and NonmetalBecause our shader uses a uniform value to determine howmetallic something is, it cannot vary across a material's surface. Thisprevents us from creating complex materials that actually represent a mix ofdifferent materials. For example, here are the albedo and normal maps for anartistic impression of computer circuitry.Albedo and normal map for circuitry. The green parts form the base of the circuit board, whilethe blue parts represent lights. These are nonmetallic. The yellow gold partsrepresent conductive circuitry, which should be metallic. On top of that aresome brown stains, for variety.Create a new material with these maps, using our lightingshader. Make it fairly smooth. Also, because the material isn't bright, itworks with Unity's default ambient environment. So set the scene's Ambient Intensity back to 1, if you stillhad it lowered to zero.Circuitry material. Using the Metallic slider, we can make the whole surface either nonmetallic, metallic, orsomething in between. This is not sufficient for the circuitry.Uniform nonmetal vs. metal. Metallic MapsThe standard shader has support for metallic maps. Thesemaps define the metallic value per texel, instead of for the whole material atonce. Here is a grayscale map which marks the circuitry as metallic, and therest as nonmetallic. Stained metal is darker, because of the semitransparentdirty layer on top.Metallic map. Add a property for such a map to our shader.Properties {
_Tint ("Tint", Color) = (1, 1, 1, 1)
_MainTex ("Albedo", 2D) = "white" {}
[NoScaleOffset] _NormalMap ("Normals", 2D) = "bump" {}
_BumpScale ("Bump Scale", Float) = 1
[NoScaleOffset] _MetallicMap ("Metallic", 2D) = "white" {}
[Gamma] _Metallic ("Metallic", Range(0, 1)) = 0
_Smoothness ("Smoothness", Range(0, 1)) = 0.1
_DetailTex ("Detail Albedo", 2D) = "gray" {}
[NoScaleOffset] _DetailNormalMap ("Detail Normals", 2D) = "bump" {}
_DetailBumpScale ("Detail Bump Scale", Float) = 1
} Do we still need the NoScaleOffset attributes?Those attributes are hints for the default shader GUI. Sono, we don't need them anymore. I keep using them in this tutorial as hints forpeople inspecting the shader code.Add the corresponding variable to ourinclude file as well.sampler2D_MetallicMfloat _M Let's create a function to retrieve themetallic value of a fragment, with the interpolators as a parameter. It simplysamples the metallic map and multiplies it with the uniform metallic value.Unity uses the R channel of the map, so we use that channel as well.struct Interpolators {
…}; floatGetMetallic (Interpolators i) {
return(_MetallicMap, i.uv.xy).r * _M} Now we can retrieve the metallic value in MyFragmentProgram.float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
albedo = DiffuseAndSpecularFromMetallic(
albedo, GetMetallic(i), specularTint, oneMinusReflectivity
…} Note that the code of MyFragmentProgram doesn't care how the metallic value is obtained. If you want to determine themetallic value a different way, you only have to change GetMetallic.Custom GUIHad we still used the default shader GUI, the metallic mapwould've appeared in the inspector. But now we have to explicitly add it to MyLightingShaderGUI,by adjusting DoMetallic. Like the standard shader, we show the mapand the slider on a single line.void DoMetallic () {
map = FindProperty("_MetallicMap");
editor.TexturePropertySingleLine(
MakeLabel(map, "Metallic (R)"), map,
FindProperty("_Metallic")
} Using a metallic map. Map or SliderThe GUI of the standard shader hides the slider when ametallic map is used. We can do so as well. It works like the bump scales,except that the value is shown when there's no texture.editor.TexturePropertySingleLine(
MakeLabel(map, "Metallic (R)"), map,
map.textureValue ? null : FindProperty("_Metallic")
); Hidden slider. Custom Shader KeywordsThe metallic slider is hidden, because the standard shaderuses either a map, or a uniform value. They aren't multiplied. When a metallicmap is provided, the uniform value is ignored. To use the same approach, wehave to distinguish between materials with and without a metallic map. This canbe done by generating two shader variants, one with and one without the map.There are already multiple variants of our shadergenerated, due to the #pragma multi_compile directives in our shader. They're based on keywords provided by Unity. Bydefining our own shader keywords, we can create the variants that we need.You can name custom keywords however you like, but theconvention is to use uppercase words with a leading underscore. In this case,we'll use _METALLIC_MAP.Where are custom keywords defined?Unity detects all custom keywords in a project, based onmulti-compile statements and which keywords are added to materials. Internally,they are converted and combined into bit masks. Which identifier a keyword getsvaries per project.In Unity 5.4, the bit mask contains 128 bits. Therefore, upto 128 keywords can exist per project. This includes Unity's keywords plus anycustom keywords that are in use. This limit used to be lower, which madeshaders with many keywords a potential hazard. Unity 5.5 will increase thelimit to 256.To add custom keywords to a material, wehave to access the material directly in our GUI. We can get to the currentlyselected material via the .target property. As this is actually an inherited property from the base
class, it has the generic
type. So we have to cast it to .
public override void OnGUI (
editor, [] properties
this.target = editor.target as ;
this.editor =
this.properties =
DoSecondary();
} Adding a keyword to a shader is done with the .EnableKeyword method, which has the keyword's name as a parameter. For removal ofa keyword, there's .DisableKeyword. Let's create a convenient method that enables or disables akeyword based on a boolean parameter.voidSetKeyword (string keyword, bool state) {
if(state) {
target.EnableKeyword(keyword);
target.DisableKeyword(keyword);
} Debugging KeywordsYou can use the debug inspector to verify that our keywordgets added to or removed from the material. You can switch the inspector todebug mode via the dropdown menu at the top right of its tab bar. The customkeywords are shown as a list in the Shader Keywords text field.Debug inspector. Any unexpected shader keywords you find here have beendefined because of previous shaders that were assigned to the material. Forexample, as soon as you selected a new material, the standard shader GUI willadd the _EMISSION keyword. They are useless to our shader, so remove them fromthe list.Shader FeaturesTo generate the shader variants, we have to add anothermulti-compile directive to our shader. Do this for both the base pass and theadditive pass. The shadow pass doesn't need it.#pragma target 3.0
#pragma multi_compile _ _METALLIC_MAP When showing the shader variants, you willsee that our custom keyword has been included. The base pass now has a total ofeight variants.SHADOWS_SCREEN VERTEXLIGHT_ON _METALLIC_MAP 8 keyword variants used in scene: VERTEXLIGHT_ONSHADOWS_SCREENSHADOWS_SCREEN VERTEXLIGHT_ON_METALLIC_MAPVERTEXLIGHT_ON _METALLIC_MAPSHADOWS_SCREEN _METALLIC_MAPSHADOWS_SCREEN VERTEXLIGHT_ON _METALLIC_MAP When using a multi-compile directive, Unity generatesshader variants for all possible combinations. Compiling all permutations cantake a lot of time, when many keywords are used. All these variants are alsoincluded in builds, which might be unnecessary.An alternative is to define a shader feature, instead of amulti-compile directive. The difference is that permutations of shader featuresare only compiled when needed. If no material uses a certain keyword, then noshader variants for that keyword are compiled. Unity also checks which keywordsare used in builds, only including the necessary shader variants.So let's used #pragma shader_feature for our custom keyword.#pragma shader_feature _ _METALLIC_MAPWhen can you use shader features?When materials are configured at design time – in theeditor only – then you can use shader features without worry. But if you adjustthe keywords of materials at run time, then you have to make sure that allvariants are included. The simplest way is to stick to multi-compile directivesfor the relevant keywords. Alternatively, you can use a shader variantcollection asset.If the shader feature is a toggle for asingle keyword, you can omit the single underscore.#pragma shader_feature _METALLIC_MAP After making this change, all shadervariants are still listed, although the order in which Unity lists them mightbe different._METALLIC_MAPSHADOWS_SCREEN VERTEXLIGHT_ON 8 keyword variants used in scene: _METALLIC_MAPVERTEXLIGHT_ONVERTEXLIGHT_ON _METALLIC_MAPSHADOWS_SCREENSHADOWS_SCREEN _METALLIC_MAPSHADOWS_SCREEN VERTEXLIGHT_ONSHADOWS_SCREEN VERTEXLIGHT_ON _METALLIC_MAP Finally, adjust the GetMetallic function in our include file. When _METALLIC_MAP is defined, sample the map. Otherwise, return the uniform value.float GetMetallic (Interpolators i) {
#if defined(_METALLIC_MAP)
return (_MetallicMap, i.uv.xy).r;
#endif} So either _MetallicMap or _Metallic is used, never both?That is correct. So the material will always have at leastone useless property. It's a little bit of overhead for the sake offlexibility.Only Setting Keywords When NeededAt the moment, we're setting the material's keyword everytime OnGUI is invoked, which is often. Logically, we only have todo this when the map property has been edited. We can check whether somethingwas changed by using the .BeginChangeCheck and .EndChangeCheck methods. The first method defines the point from which we want to begintracking changes. The second method marks the end, and returns whether a changewas made.By putting these methods before and after the invocation of TexturePropertySingleLine, we can easily detect whether themetallic line was edited. If so, we set the keyword.
void DoMetallic () {
map = FindProperty("_MetallicMap");
.BeginChangeCheck();
editor.TexturePropertySingleLine(
MakeLabel(map, "Metallic (R)"), map,
map.textureValue ? null : FindProperty("_Metallic")
if(.EndChangeCheck()) {
SetKeyword("_METALLIC_MAP", map.textureValue);
} Doesn't this also trigger when _Metallic is changed?Yes, this code sets the keyword both when the map ischanged and also when the uniform value is edited. That is more often thanrequired, but still much better than all the time.Does this work with undo and redo?Yes. The
methods that we're using to show properties take care of recording the oldobject's state.Smoothness MapsLike the metallic map, smoothness can also be defined via amap. Here is a grayscale smoothness texture for the circuitry. The metal partsare the smoothest, followed by the lights. The rest is fairly rough. The stainsare smoother than the board, so the texture is lighter there.Smoothness map. Unity's standard shader expects smoothness to be stored inthe alpha channel. In fact, it is expected that the metallic and smoothnessmaps are combined in the same texture. Because DXT5 compressed the RGB and theA channels separately, merging the maps into one DXT5 texture produces the samequality as using two DXT1 textures. This doesn't require less memory, butallows us to retrieve both metallic and smoothness from a single texturesample, instead of two.Here is a texture that combines both maps. Althoughmetallic only needs the R channel, I still filled the RGB channels with themetallic value. Smoothness uses the alpha channel.Metallic and smoothness map. Determining SmoothnessWhen there is a metallic map, we can get the smoothnessfrom there. Otherwise, we use the uniform _Smoothness property. Add a GetSmoothness function to our include file to takecare of this. It's almost exactly like GetMetallic.floatGetSmoothness (Interpolators i) {
#if defined(_METALLIC_MAP)
return(_MetallicMap, i.uv.xy).a;
#endif} Aren't we still sampling the texture twice?Remember that the shader compiler gets rid of duplicatecode. We're sampling the same texture in two different functions, but thecompiled code will sample the texture only once. We don't have to explicitlycache these things.Actually, the standard shader has twodifferent properties for smoothness. One is the stand-alone uniform value, likewe have. The other is a scalar that modulates the smoothness map. Let's keep itsimple and use the _Smoothness property for both purposes. This means that you have to set it to 1 to get theunmodified smoothness map values.
return (_MetallicMap, i.uv.xy).a * _Smoothness; Use this new function to get the smoothnessin MyFragmentProgram.float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
return UNITY_BRDF_PBS(
albedo, specularTint,
oneMinusReflectivity, GetSmoothness(i),
i.normal, viewDir,
CreateLight(i), CreateIndirectLight(i, viewDir)
);} But that's not the only place where we needthe smoothness. CreateIndirectLight makes use of it as well. We could add a smoothness parameter tothis function. But we can also just invoke GetSmoothness a secondtime. The shader compiler will detect the duplicate code and optimize it away.UnityIndirect CreateIndirectLight (Interpolators i, float3 viewDir) {
Unity_GlossyEnvironmentData envD
envData.roughness = 1 - GetSmoothness(i);
…} Mapped smoothness, at fullstrength. What are those square artifacts along the edges of themetal strips?Those artifacts are caused by the DXT5nm texturecompression of the normal map. Narrow ridges that are too thin cannot beapproximated correctly, especially if they aren't aligned with the UV axes. Thesharp diagonal edges in the circuitry are the worst case for this kind ofcompression. This limitation becomes clearly visible on surfaces that aremetallic and very smooth. Otherwise, it isn't as obvious.Using an uncompressed normal map. Combining Smoothness with AlbedoCombining metallic and smoothness maps into a singletexture is fine, when you need both of them. Metallic parts are nearly alwayssmoother than the other bits. So when you need a metallic map, you practicallyalways want a smoothness map too. But you might need a smoothness map, withoutmixing metals and nonmetals. In that case, the metallic map is useless.For opaque materials that don't require a metallic map, itis possible to store the smoothness in the alpha channel of the albedo map. Asthis practice is common, the standard shader supports packing smoothness ineither the metallic map or the albedo map. Let's support this too.Switching Between KeywordsLike the standard shader, we have to add an option to pickthe smoothness source to our GUI. Although the standard shader only supportsthe choice between two maps, let's go ahead and include a uniform smoothness asa third option as well. To represent these options, define an enumeration typeinside MyLightingShaderGUI.enumSmoothnessSource {
Uniform, Albedo, Metallic
} When the albedo map is used as the smoothness source, we'lladd the _SMOOTHNESS_ALBEDO keywordto the material. When the metallic source is used, we'll add _SMOOTHNESS_METALLIC instead. The uniformoption corresponds with no keyword.The standard shader also uses a float property to trackwhich option a material uses, but we can make do with the keywords alone. TheGUI can determine the current choice by checking which keyword is enabled. Thiscan be done via the .IsKeywordEnabled method, for which we'll create a convenient wrapper.boolIsKeywordEnabled (string keyword) {
returntarget.IsKeywordEnabled(keyword);
} Now DoSmoothness can figureout the current smoothness source of the selected material.void DoSmoothness () {
SmoothnessSourcesource = SmoothnessSource.U
if(IsKeywordEnabled("_SMOOTHNESS_ALBEDO")) {
source = SmoothnessSource.A
elseif (IsKeywordEnabled("_SMOOTHNESS_METALLIC")) {
source = SmoothnessSource.M
} To show the options, we can use the .EnumPopup method. Increase the indent level an additional step, to match the layout ofthe standard shader..indentLevel += 2;
editor.ShaderProperty(slider, MakeLabel(slider));
.indentLevel += 1;
.EnumPopup(MakeLabel("Source"), source);
.indentLevel -= 3; Smoothness source popup. EnumPopup is a basic editor widgetwhich creates a popup list for any enumeration. It returns the selected value.If the user doesn't choose a new option, this value is the same as the originalchoice. Otherwise, it is different. So to know which option was chosen, we haveto assign it back to the source variable. As the method works with the genericenumeration type, we have to cast it to SmoothnessSource.source = (SmoothnessSource).EnumPopup(
MakeLabel("Source"), source
); If a change has been made, we can use thesource variable to control which keyword should be set, if any..BeginChangeCheck();
source = (SmoothnessSource).EnumPopup(
MakeLabel("Source"), source
if(.EndChangeCheck()) {
SetKeyword("_SMOOTHNESS_ALBEDO", source == SmoothnessSource.Albedo);
SetKeyword(
"_SMOOTHNESS_METALLIC", source == SmoothnessSource.Metallic
} Smoothness from the metallic map. Supporting UndoWe can now change the smoothness source, but it doesn'tsupport undo and redo actions yet. Because we're using a basic widget, we haveto manually signal that we performed an action that should support undo. Thiscan be done via the .RegisterPropertyChangeUndo method, which has a descriptive label as a parameter. Create a wrapper for thismethod as well.voidRecordAction (string label) {
editor.RegisterPropertyChangeUndo(label);
} RecordAction must be invoked before the change that we're about to make. Itcreates a snapshot of the old state, so an undo action can revert to it.if (.EndChangeCheck()) {
RecordAction("Smoothness Source");
SetKeyword("_SMOOTHNESS_ALBEDO", source == SmoothnessSource.Albedo);
SetKeyword(
"_SMOOTHNESS_METALLIC", source == SmoothnessSource.Metallic
} Smoothness VariantsTo support all three options, add a shader feature thatselects between no keyword, _SMOOTHNESS_ALBEDO,and _SMOOTHNESS_METALLIC. Asbefore, both the base and additive passes have to support it.#pragma shader_feature _METALLIC_MAP
#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC In GetSmoothness, start with a smoothness of1. Then check whether the albedo source is chosen. If so, replace the 1 withthe albedo map. Otherwise, check whether the metallic source is chosen, and ifso use the metallic map instead. Of course this is only sensible when thematerial actually uses a metallic map, so check for that as well.After that, return whatever smoothness value we got,multiplied with the value of the _Smoothness property. If we end up with a variant that does not use a map, the compilerwill optimize away the multiplication with 1.float GetSmoothness (Interpolators i) {
floatsmoothness = 1;
#if defined(_SMOOTHNESS_ALBEDO)
smoothness = (_MainTex, i.uv.xy).a;
#elif defined(_SMOOTHNESS_METALLIC) && defined(_METALLIC_MAP)
smoothness = (_MetallicMap, i.uv.xy).a;
returnsmoothness * _S} Lava MaterialHere are example albedo and normal maps for an artisticimpression of cooled lava. The material isn't metallic, but does have varyingsmoothness. So the smoothness values are stored in the alpha channel of thealbedo map.Albedo with smoothness, and normals. Create a material with these maps,using the albedo source option for smoothness.Lava material. When using the albedo source, thegrayish lumps turn out to be significantly smoother than the reddish gulleys.Uniform vs. mapped, using albedo alpha.Emissive SurfacesSo far, we've only worked with materials that reflectlight, via either diffuse or specular reflection. We need a light source to seesuch surfaces. But there are also surfaces that emit light themselves. Forexample, when something becomes hot enough, it starts to glow. You don't need adifferent light source to see that. The standard shader supports this via anemissive map and color, and so will we.Mapped and UniformAdd properties for an emission map and color to our shader.Both should be black by default, meaning that no light is emitted. As we onlycare about the RGB channels, we can omit the fourth component of the defaultcolor.[NoScaleOffset] _EmissionMap ("Emission", 2D) = "black" {}
_Emission ("Emission", Color) = (0, 0, 0) Many materials won't have an emission map,so let's use a shader feature to create variants without and with an emissionmap. Because we only have to add the emitted light once, only include thefeature in the base pass.#pragma shader_feature _METALLIC_MAP
#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC
#pragma shader_feature _EMISSION_MAP Add the required sampler and floatvariables to the include file.sampler2D_EmissionMfloat3_E Create a GetEmission function toretrieve the emitted color, if any. When there's a map, sample it and multiplywith the uniform color. Otherwise, just return the uniform color. But onlybother to do this in the base pass. In all other cases, the emission is zero,which the compiler will optimize away.float3GetEmission (Interpolators i) {
#if defined(FORWARD_BASE_PASS)
#if defined(_EMISSION_MAP)
return(_EmissionMap, i.uv.xy) * _E
#endif} As the emission comes from the objectitself, it is independent of the reflected light. So just add it to the finalcolor.float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
float4color = UNITY_BRDF_PBS(
albedo, specularTint,
oneMinusReflectivity, GetSmoothness(i),
i.normal, viewDir,
CreateLight(i), CreateIndirectLight(i, viewDir)
color.rgb += GetEmission(i);
return} Adding Emission to the GUICreate a DoEmission method inside MyLightingShaderGUI.The quickest way to do this is by duplicating DoMetallic andmaking a few changes.
void DoEmission () {
map = FindProperty("_EmissionMap");
.BeginChangeCheck();
editor.TexturePropertySingleLine(
MakeLabel(map, "Emission (RGB)"), map, FindProperty("_Emission")
if (.EndChangeCheck()) {
SetKeyword("_EMISSION_MAP", map.textureValue);
} Include it in the main maps section.
void DoMain () {
DoNormals();
DoEmission();
editor.TextureScaleOffsetProperty(mainTex);
} Inspector with emission map and color. HDR EmissionThe standard shader doesn't use a regular color foremission. Instead, it supports high-dynamic-range colors. This means that theRGB components of the color can be higher than 1. This way, you can representextremely bright light.Can we see colors that are brighter than 1?In real life, there is no hard limit to the amount ofphotons that you can be bombarded with. The sun is very bright, blindingly so.However, computer displays are limited. You cannot go higher than 1. How brightthat is depends on the brightness of the display.To use HDR colors in a meaningful wall, you have to performtone mapping. This means that you convert one range of colors to another range.We'll investigate tone mapping in a future tutorial. HDR colors are also oftenused to create bloom effects.As color properties are float vectors, we are not limitedto just values in the 0–1 range. However, the standard color widget is designedwith this limit in mind. Fortunately,
contains the TexturePropertyWithHDRColor method, which isspecifically made for a texture plus an HDR color property. It requires twoadditional parameters. First, configuration options for the HDR range. Second,whether the alpha channel should be shown, which we do not want.
void DoEmission () {
editor.TexturePropertyWithHDRColor(
MakeLabel("Emission (RGB)"), map, FindProperty("_Emission"),
emissionConfig, false
} The HDR color widget is configured via a
object. This object contains the allowed brightness and exposure range. Thestandard shader uses 0–99 for brightness, and nearly zero to 3 for exposure.We'll simply use the same ranges.
static emissionConfig =
new(0f, 99f, 1f / 99f, 3f); Inspector with HDR emission. The extra value after the color picker corresponds with thebrightness of the color. This is simply the largest of the RGB channels. Aquick way to switch the emissive color to black or white is to set this valueto 0 or 1.Emissive LavaHere is an emission map for the lava material. It makes thelava in the gulleys glowing hot. You can change the brightness and tint of theemission by adjusting the uniform color.Emission map for lava.I assigned the emission map, but it doesn't show?In that case, the uniform emission color is still black. Tosee the map at full strength, set the color to white.The standard shader automatically sets the emission colorto white, when a texture has been assigned while the color was black. You couldadd this functionality as well. However, that behavior is based on assumptionswhich might not be true for some people, which can lead to a lot of userfrustration.Glowing lava, lit and unlit. Emissive CircuitryAnd here is an emission map for the circuitry lights.Emission map for circuitry. The lights have varying brightness, and the stains affectthem as well.Circuitry with functioning lights, lit and unlit.Does the emitted light illuminate other objects?The emission is part of the material only. It doesn'taffect the rest of the scene. However, Unity's global illumination system canpick up this emitted light and add it to the indirect illumination data. Wewill investigate global illumination in a future tutorial.The next tutorial will add even more features to ourshader, bringing it closer to feature parity with the standard shader. It willbe released in November 2016.
Unity渲染教程(九):复杂材质
版权所有,禁止匿名转载;禁止商业使用;禁止个人使用。
翻译:赵菁菁(轩语轩缘)
审校:李笑达(DDBC4747)创建一个自定义着色器GUI混合金属和非金属使用nonuniform光滑度支持发射表面这是渲染教程系列的第九部分。上一次,我们加入了对于环境贴图的支持,在这一部分,我们会组合多种纹理来创建复杂材质。但是在我们开始之前,我们需要为我们的着色器创造一个更好的GUI。本教程的运行环境是Unity 5.4.1f1。同样的着色器,不同的贴图用户界面
到目前为止,我们一直都为我们的材质使用Unity默认的材质监视器,它很耐用,但是Unity的标准着色器长得非常不一样。仿照着标准着色器,让我们一起来为我们自己的着色器创建一个自定义监视器吧。我们默认的监视器和标准着色器监视器着色器GUI
我们可以通过添加一个继承UnityEditor.的类创建一个自定义监视器。因为它是一个编辑器类,所以应该把脚本文件放到Editor文件夹下。12345using UnityEusing UnityE
public class MyLightingShaderGUI : ShaderGUI {}我们不需要继承吗?Unity 4.1支持通过继承自定义材质监视器,你也可以依旧这样做,但是在5.0版本中是作为备选被加入的,它的创建与Substance材质有一些关系。Unity为标准着色器使用,所以我们也使用它。要使用一个自定义GUI,你需要向着色器加入CustomEditor指令,紧跟着一个字符串,字符串包含要使用的GUI类的名字。12345Shader "Custom/My First Lighting Shader" {
CustomEditor "MyLightingShaderGUI"} 类能放入命名空间吗?
可以。你需要在着色器中指定完全限定的类名。1CustomEditor "MyNamespace.MyShaderGUI"
为了替换默认监视器,我们需要重写.OnGUI方法,该方法有两个参数,第一个参数是的引用,这个对象管理当前被选材质的监视器;第二个参数是包含材质属性的数组。1234567public class MyLightingShaderGUI : ShaderGUI {
public override void OnGUI (
MaterialEditor editor, MaterialProperty[] properties
}}在这个方法内部,我们已经创建了我们自己的GUI。因为我们还什么都没做,所以监视器还是空的。创建一个标签
标准着色器GUI被分成两部分,一部分针对主要贴图,另一部分针对次要贴图。我们会在我们的GUI中采用相同的布局。为了保持代码整洁,我们会为GUI不同的部分采用单独的方法。我们从主要部分以及其标签入手。123456public override void OnGUI (
MaterialEditor editor, MaterialProperty[] properties
DoMain();}
void DoMain() {
GUILayout.Label("Main Maps");}主要贴图标签 是如何工作的?UnityEditor是用Unity的立即模式UI创建的,这是Unity老的UI系统,在当今基于画布的系统之前,它也用于游戏内UI。立即模式UI的基础是GUI类,它包含创建UI小工具的方法。你需要利用矩形来精确定位每个元素。类提供了相同的功能,但是它会使用简单布局系统自动定位小工具。除此之外, 类和 类提供了针对编辑器UI的小工具和特征的访问。标准着色器有一个粗体标签,所以我们也想要一个粗体标签,通过向标签添加GUI样式就可以实现,这样一来就是.boldLabel。1GUILayout.Label("Main Maps", EditorStyles.boldLabel);粗体标签.显示Albedo
为了显示我们材质的属性,我们需要在我们的方法中访问它们。我们会把OnGUI的参数传递到其他所有方法中,但是这会导致大量重复代码。我们不这么做,我们把它们放到域中。12345678MaterialEMaterialProperty[]
public override void OnGUI (
MaterialEditor editor, MaterialProperty[] properties
this.editor =
this.properties =
DoMain();}每次OnGUI被调用时我们都需要拷贝引用吗?当一个新的 实例被创建时, 会做出决定。目前来讲,这会在一个材质被选了的时候发生,就像你可能预期的那样。但是它也可能在撤销会重做动作执行的时候发生。这意味着你不能继续依赖实例了。每次,它都会是一个新的对象实例,你可以把OnGUI想成它好像是一个静态方法,尽管它并不是。
Albedo贴图在标准着色器中第一次展现。这是主要纹理,它的属性在属性数组的某一位置设置,它的数组下标取决于我们着色器中定义属性的顺序。但是通过名称查找的鲁棒性更强一些。包括FindProperty方法,它就是干这件事的,通过给定名称和属性数组找到对应结果。1234void DoMain () {
GUILayout.Label("Main Maps", EditorStyles.boldLabel);
MaterialProperty mainTex = FindProperty("_MainTex", properties);}
除了纹理属性之外,我们也需要定义标签的内容,这可以通过实现,它是一个简单的容器类。12MaterialProperty mainTex = FindProperty("_MainTex", properties);GUIContent albedoLabel = new GUIContent("Albedo");
但是我们已经在我们的着色器中为主要纹理Albedo命名了,我们只能使用已定义好的名称,我们可以通过属性访问到。1GUIContent albedoLabel = new GUIContent(mainTex.displayName);
为了创建那些小的纹理小工具,我们必须依赖我们已经引用的编辑器,它具有许多绘制这样小工具的方法。123MaterialProperty mainTex = FindProperty("_MainTex", properties);GUIContent albedoLabel = new GUIContent(mainTex.displayName);editor.TexturePropertySingleLine(albedoLabel, mainTex);Albedo贴图
这就是看起来像标准着色器的开始!但是当你鼠标经过属性标签时,那个监视器还有提示信息,在Albedo贴图的情况中,上面写着Albedo (RGB) 和Transparency (A)。
我们也可以加一个提示信息,很简单,只要向标签内容中添加就可以了。因为我们还不支持透明度,我们就只使用Albedo(RGB)。12GUIContent albedoLabel =
new GUIContent(mainTex.displayName, "Albedo (RGB)");带提示信息的Albedo
TexturePropertySingleLine方法有可以与多个(最多三个)属性作用的变体,第一个应该是纹理,但是其他可能就是别的什么了,它们都会被放在一行中,我们可以用这来显示纹理旁边的取色工具。12MaterialProperty tint = FindProperty("_Tint", properties);editor.TexturePropertySingleLine(albedoLabel, mainTex, tint);Albedo贴图和取色
让我们直接跳到主要部分的底部,那是显示主要纹理贴砖和偏移量的位置,这可以通过.TextureScaleOffsetProperty方法实现。12editor.TexturePropertySingleLine(albedoLabel, mainTex, tint);editor.TextureScaleOffsetProperty(mainTex);便利方法不用现有的FindProperty 方法,让我们创建一个只需要一个名称参数的方法,充分利用我们属性域的优势,这会让我们的代码更易读。123MaterialProperty FindProperty (string name) {
return FindProperty(name, properties);}在DoMain中换成用这个方法,我们也可以直接把色彩属性传递给TexturePropertySingleLine方法,我们不会在其它地方用到它。 123456789101112void DoMain () {
GUILayout.Label("Main Maps", EditorStyles.boldLabel);
MaterialProperty mainTex = FindProperty("_MainTex");
GUIContent albedoLabel =
new GUIContent(mainTex.displayName, "Albedo (RGB)");
editor.TexturePropertySingleLine(
albedoLabel, mainTex, FindProperty("_Tint")
editor.TextureScaleOffsetProperty(mainTex);}
让我们再建立一个方法来配置标签的内容。我们只需要使用一个单例静态就可以,我们把它的文本和提示信息修改了就好。因为我们可能不总是需要提示信息,让我们把它变为可选择的,同时带有一个默认的参数值。1234567static GUIContent staticLabel = new GUIContent();
static GUIContent MakeLabel (string text, string tooltip = null) {
staticLabel.text =
staticLabel.tooltip =
return staticL}
如果我不非要总是从属性中抽取出显示名称的话,就会更加方便,所以再建立一个MakeLabel变量来做这件事吧。1234567static GUIContent MakeLabel (
MaterialProperty property, string tooltip = null
staticLabel.text = property.displayN
staticLabel.tooltip =
return staticL}现在DoMain变得更小了,我们日后的方法也会遵循这一重构流程。1234567891011void DoMain () {
GUILayout.Label("Main Maps", EditorStyles.boldLabel);
MaterialProperty mainTex = FindProperty("_MainTex");
editor.TexturePropertySingleLine(
MakeLabel(mainTex, "Albedo (RGB)"), mainTex, FindProperty("_Tint")
editor.TextureScaleOffsetProperty(mainTex);}显示法线
下一个需要展示的纹理是法线贴图。不要把所有代码都放在DoMain中,把代码分派到一个独立的DoNormals方法中,在Albedo行之后、铺砖和偏移量之前调用。12DoNormals();editor.TextureScaleOffsetProperty(mainTex);新的DoNormals方法会检索贴图属性,之后展现出来,标准着色器不会提供任何额外的提示信息,所以我们也不会。1234void DoNormals () {
MaterialProperty map = FindProperty("_NormalMap");
editor.TexturePropertySingleLine(MakeLabel(map), map);}当然也有凹凸程度,所以把它加到代码中。123editor.TexturePropertySingleLine(
MakeLabel(map), map, FindProperty("_BumpScale"));法线贴图和凹凸程度
标准着色器只会在法线贴图赋值给材质的时候显示凹凸程度,我们也这样做,检查属性是否引用了一个纹理,如果引用了,就显示凹凸程度,否则就只是给TexturePropertySingleLine赋空值。1234editor.TexturePropertySingleLine(
MakeLabel(map), map,
map.textureValue ? FindProperty("_BumpScale") : null);隐藏的凹凸程度显示金属质感与光滑度金属和光滑度属性是简单的浮点范围。至少目前我们可以通过通用的.ShaderProperty方法显示它们。与纹理方法不同,该方法的第一个参数是属性,第二个参数是标签内容。12345678910111213141516171819202122void DoMain () {
editor.TexturePropertySingleLine(
MakeLabel(mainTex, "Albedo (RGB)"), mainTex, FindProperty("_Tint")
DoMetallic();
DoSmoothness();
DoNormals();
editor.TextureScaleOffsetProperty(mainTex);}
void DoMetallic () {
MaterialProperty slider = FindProperty("_Metallic");
editor.ShaderProperty(slider, MakeLabel(slider));}
void DoSmoothness () {
MaterialProperty slider = FindProperty("_Smoothness");
editor.ShaderProperty(slider, MakeLabel(slider));}金属质感与光滑度我们可以让这些属性与其他标签对齐,通过提升编辑器的缩进级别。在这种情况下,两步。缩进级别可以通过静态.indentLevel属性调整,确保之后把它设置回原来的值。12345678910111213void DoMetallic () {
MaterialProperty slider = FindProperty("_Metallic");
EditorGUI.indentLevel += 2;
editor.ShaderProperty(slider, MakeLabel(slider));
EditorGUI.indentLevel -= 2;}
void DoSmoothness () {
MaterialProperty slider = FindProperty("_Smoothness");
EditorGUI.indentLevel += 2;
editor.ShaderProperty(slider, MakeLabel(slider));
EditorGUI.indentLevel -= 2;}锯齿属性显示次要贴图
次要贴图与主要贴图看起来很像,所以创建一个DoSecondary方法,它可以管理粗体标签,细节纹理,以及它的贴砖和偏移量。1234567891011121314151617181920public override void OnGUI (
MaterialEditor editor, MaterialProperty[] properties
this.editor =
this.properties =
DoSecondary();
void DoSecondary () {
GUILayout.Label("Secondary Maps", EditorStyles.boldLabel);
MaterialProperty detailTex = FindProperty("_DetailTex");
editor.TexturePropertySingleLine(
MakeLabel(detailTex, "Albedo (RGB) multiplied by 2"), detailTex
editor.TextureScaleOffsetProperty(detailTex);}
调整我们着色器中细节纹理的显示名称,让它与标准着色器一致。1_DetailTex ("Detail Albedo", 2D) = "gray" {}次要贴图
细节法线贴图与主要法线贴图的效果一样,有趣的是,标准着色器GUI没有隐藏细节凹凸程度,但是我们要求一致性,所以当没有细节法线贴图的时候,我们把细节凹凸程度也隐藏。123456789101112void DoSecondary () {
DoSecondaryNormals();
editor.TextureScaleOffsetProperty(detailTex);}void DoSecondaryNormals () {
MaterialProperty map = FindProperty("_DetailNormalMap");
editor.TexturePropertySingleLine(
MakeLabel(map), map,
map.textureValue ? FindProperty("_DetailBumpScale") : null
);}完整监视器混合金属与非金属
因为我们的着色器使用uniform值来判断某个物体有多金属化,对于一个材质表面来说,它不能改变,这会阻碍我们创建复杂材质,这些材质实际上代表了不同材质的混合。例如,这里有计算机电路的艺术印象的Albedo和法线贴图。 电路的Albedo和法线贴图
绿色部分形成了电路板的基础,蓝色部分代表光,这些是非金属的。金黄色部分代表导电回路,这里应该是金属的。在顶部有一些棕色污点,是为了使变化丰富。
用这些贴图创建一个新的材质,同时使用我们的光照着色器,让效果相对光滑些。同时,因为材质不是明亮的,它与Unity默认的周边环境相互作用,所以如果你的场景的Ambient Intensity值比0小,把它设回1。电路材质
使用Metallic滑块,我们可以让整个表面变得非金属、金属或介于两者之间,这对于电路并不足够。 uniform非金属和金属金属度贴图
标准着色器支持金属度贴图,这些贴图每纹素都定义了金属值,而不是一次性为整个材质都定义。这里有一个灰度贴图,它把电路作为金属标记,余下的是非金属。有污渍的金属暗一些,因为表层上有半透明的脏东西。金属度贴图为着色器中这样的贴图添加一个属性。123456789101112131415Properties {
_Tint ("Tint", Color) = (1, 1, 1, 1)
_MainTex ("Albedo", 2D) = "white" {}
[NoScaleOffset] _NormalMap ("Normals", 2D) = "bump" {}
_BumpScale ("Bump Scale", Float) = 1
[NoScaleOffset] _MetallicMap ("Metallic", 2D) = "white" {}
[Gamma] _Metallic ("Metallic", Range(0, 1)) = 0
_Smoothness ("Smoothness", Range(0, 1)) = 0.1
_DetailTex ("Detail Albedo", 2D) = "gray" {}
[NoScaleOffset] _DetailNormalMap ("Detail Normals", 2D) = "bump" {}
_DetailBumpScale ("Detail Bump Scale", Float) = 1}我们还需要NoScaleOffset 属性吗?
那些属性是默认着色器GUI的提示,那么,我们就不再需要它们了,我在教程中保留它们,只是为了提示那些需要检验着色器代码的人。
也要在我们的include文件中加入相应的变量。12sampler2D _MetallicMfloat _M
让我们建立一个函数来检索一个片段的金属值,利用Interpolators作为参数,它简单地对金属度贴图进行了采样,并且用它和uniform金属值相乘。Unity用贴图的R通道,所以我们也用这个通道。1234567struct Interpolators {
float GetMetallic (Interpolators i) {
return tex2D(_MetallicMap, i.uv.xy).r * _M}现在我们可以检索MyFragmentProgram中的金属值。123456789float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
albedo = DiffuseAndSpecularFromMetallic(
albedo, GetMetallic(i), specularTint, oneMinusReflectivity
注意MyFragmentProgram代码不关心金属值是如何得到的,如果你想要以不同的方式定义金属值,你只需要修改GetMetallic。自定义 GUI
如果我们仍然使用默认着色器GUI,金属度贴图就会出现在监视器中了,但是现在我们需要通过调整DoMetallic把它明确地加入到MyLightingShaderGUI中,就像标准着色器一样,我们把贴图和滑块在一行中显示。1234567void DoMetallic () {
MaterialProperty map = FindProperty("_MetallicMap");
editor.TexturePropertySingleLine(
MakeLabel(map, "Metallic (R)"), map,
FindProperty("_Metallic")
);}使用一个金属度贴图贴图或者滑块
当使用金属度贴图时,标准着色器的GUI会隐藏滑块,我们也可以这么做,它与凹凸程度的效果一样,当没有纹理的时候就会显示值。1234editor.TexturePropertySingleLine(
MakeLabel(map, "Metallic (R)"), map,
map.textureValue ? null : FindProperty("_Metallic"));隐藏的滑块自定义着色器关键字
金属滑块隐藏了,因为标准着色器用一个贴图或者一个uniform值,它们不是乘在一起的。当提供了金属度贴图时,uniform值就会被忽略。为了使用相同的方式,我们需要区分带有和没有金属度贴图的材质,这可以通过个两个着色器变体实现,一个有贴图,一个没有贴图。
由于着色器中的#pragma multi_compile指令,我们的着色器已经有多个变体生成,它们是基于Unity提供的关键字。通过定义我们自己的着色器关键字,我们可以创建我们需要的变体。
你可以任意命名自定义关键字,但是传统是使用一个以下划线开头的大写单词命名,在这个例子中,我们使用_METALLIC_MAP。自定义关键字在哪里定义?Unity检测项目中的所有自定义关键字,基于multi-compile语句,这些语句把关键字加入材质中。在内部,它们被转换,合并入位掩码 , 而且每个项目都可以得到不同的关键字标识符。在Unity 5.4中,位掩码有128位。因此,每个项目可以存在最多128个关键字。其中包括Unity的关键字,加上任何正在使用的自定义关键字。这个限制通常会低一些,这会让具有许多关键字的着色器成为潜在的危险。Unity 5.5会把限制值增加到256。要向材质中添加自定义关键字,我们必须在我们的GUI中直接访问材质。我们可以通过.target属性得到当前选择的材质,这实际上是来自Editor基类的一个继承属性,它是通用的Object类型,所以我们必须把它强制转换成Material。12345678910111213M
MaterialProperty[]
public override void OnGUI (
MaterialEditor editor, MaterialProperty[] properties
this.target = editor.target as M
this.editor =
this.properties =
DoSecondary();}.EnableKeyword方法可以向着色器中添加一个关键字,其中关键字的名称作为参数。对于移除一个关键字,有.DisableKeyword方法。我们一起来创建一个便利的方法,它可以通过一个布尔型参数启用或者禁用一个关键字。12345678void SetKeyword (string keyword, bool state) {
if (state) {
target.EnableKeyword(keyword);
target.DisableKeyword(keyword);
}}调试关键字
你可以使用调试监视器来证实我们的关键字已经加入材质或者从材质中移除,你可以通过选项卡右上的下拉菜单把监视器转到调试模式。自定义关键字在Shader Keywords文本域中以列表形式显示。调试监视器
你在这里找到的任何预料不到的着色器关键字都已经被定义,因为之前的着色器已经赋值给材质。例如,你选择一个新材质,标准着色器GUI就会添加一个_EMISSION关键字, 它们对于我们的着色器是毫无用处的,所以把它们从列表中移走。着色器特征
为了生成着色器变体,我们需要向着色器添加另一个multi-compile指令,对于基础通道和附加通道都要这么做,阴影通道不需要。12#pragma target 3.0#pragma multi_compile _ _METALLIC_MAP
当显示着色器变体时,你会发现我们的自定义关键字已经包含进来了,基础通道现在有总共八个变体。123456789101112131415SHADOWS_SCREEN VERTEXLIGHT_ON _METALLIC_MAP
8 keyword variants used in scene:
&no keywords="" defined=""&VERTEXLIGHT_ONSHADOWS_SCREENSHADOWS_SCREEN VERTEXLIGHT_ON_METALLIC_MAPVERTEXLIGHT_ON _METALLIC_MAPSHADOWS_SCREEN _METALLIC_MAPSHADOWS_SCREEN VERTEXLIGHT_ON _METALLIC_MAP&/no&
当使用multi-compile指令时,Unity会为所有可能的组合生成着色器变体。当使用很多关键字时,编译所有的排列会花费很多时间,所有这些变体都包含在构建项目中,这可能是不必要的。
另一个可行方法是定义一个着色器 ,而不是multi-compile指令,区别在于着色器特征的排列只在需要时编译,如果没有材质使用某个关键字,那么关于此关键字的着色器变体将不会被编译。Unity也会检查哪些关键字用在构建项目阶段,只会包含必需的着色器变体。那么,让我们为我们的自定义关键字使用 #pragma shader_feature。1#pragma shader_feature _ _METALLIC_MAP你什么时候可以使用着色器特征?
当材质在设计阶段配置时——只在编辑器中——之后你大可放心使用着色器特征。但是如果你在运行时调整了材质的关键字,那么你必须确保所有的变体都包含进来了,最简单的方法就是紧跟相关关键字multi-compile指令。或者,你可以使用一个着色器变体集资源。如果着色器特征是一个关键词的触发器,你可以忽略单下划线。1#pragma shader_feature _METALLIC_MAP
在做了这个改变之后,所有的变体仍旧列了一张表,尽管Unity排列的顺序可能不一样。12345678910111213141516_METALLIC_MAPSHADOWS_SCREEN VERTEXLIGHT_ON
8 keyword variants used in scene:
&no keywords="" defined=""&_METALLIC_MAPVERTEXLIGHT_ONVERTEXLIGHT_ON _METALLIC_MAPSHADOWS_SCREENSHADOWS_SCREEN _METALLIC_MAPSHADOWS_SCREEN VERTEXLIGHT_ONSHADOWS_SCREEN VERTEXLIGHT_ON _METALLIC_MAP&/no&
最后,在我们的include文件中调整GetMetallic函数,当定义了_METALLIC_MAP 之后,取样贴图,否则就返回uniform值。1234567float GetMetallic (Interpolators i) {
#if defined(_METALLIC_MAP)
return tex2D(_MetallicMap, i.uv.xy).r;
#endif}所以只能用_MetallicMap或者_Metallic之一,而不是全用?确实是这样,所以材质总会有至少一个无用的属性,为了灵活性,得有点开销。只在需要的时候设置关键字此刻,每当OnGUI被调用的时候我们就设置材质的关键词,这种调用很频繁。逻辑上,我们只需要在贴图属性已经被编辑的时候做这件事,我们可以检查通过使用.BeginChangeCheck和 .EndChangeCheck方法是否有改变产生。第一个方法定义了我们想要开始追踪变化的点,第二个方法标记了结尾,并且返回是否产生了变化。通过把这些方法放在TexturePropertySingleLine之前和之后,我们可以很容易地检测金属行是否被编辑了。如果是这样,我们设置关键字。1234567891011void DoMetallic () {
MaterialProperty map = FindProperty("_MetallicMap");
EditorGUI.BeginChangeCheck();
editor.TexturePropertySingleLine(
MakeLabel(map, "Metallic (R)"), map,
map.textureValue ? null : FindProperty("_Metallic")
if (EditorGUI.EndChangeCheck()) {
SetKeyword("_METALLIC_MAP", map.textureValue);
}}当_Metallic改变时是否也会触发该方法?是的,当贴图改变时,当uniform值被编辑时,代码都会设置关键字。这比要求的频繁,但是也比总设置强。这会支持撤销和重做吗?是的。我们正在使用显示属性的方法来负责记录原对象的状态。光滑度贴图
就像金属度贴图一样,光滑度也可以通过一个贴图定义。这里的电路有一个灰度光滑度纹理。金属部分是最光滑的,之后是灯泡,剩余部分非常粗糙。污渍比电路板光滑,所以那里的纹理更浅。光滑度贴图
Unity的标准着色器希望把光滑度存储在alpha通道中。实际上,希望金属度和光滑度贴图可以在相同的纹理中结合起来,因为DXT5把RGB和A通道分别压缩,把贴图合并到一个DXT5纹理中,可以产生与使用两个DXT1纹理相同的质量。这不会要求更少的内存,但是可以让我们从一次纹理采样中就检索到金属度和光滑度,而不是两次。
有一个纹理合并了贴图。尽管金属度只需要R通道,我依旧会用金属度值填充RGB通道,光滑度使用alpha通道。金属度和光滑度贴图决定光滑度
当存在一个金属度贴图时,我们可以从中得到光滑度,否则的话,我们就使用uniform _Smoothness属性,向我们的include文件中加入一个GetSmoothness函数来管理它,这个函数与GetMetallic几乎一模一样。1234567float GetSmoothness (Interpolators i) {
#if defined(_METALLIC_MAP)
return tex2D(_MetallicMap, i.uv.xy).a;
#endif}我们不还是要取样纹理两次吗?
记住着色器编译器摆脱了重复代码,我们正在不同的函数中采样相同的纹理,但是编译好的代码只会取样纹理一次,我们不需要明确地对这些内容进行缓存。
实际上,标准着色器有两个不同的光滑度属性,一个是单独的uniform值,就像我们的一样,另一个是调整光滑度贴图的标量。让我们简单点做,为了两个目标用_Smoothness属性,这意味着你需要把它设为1来获取未修改的光滑度贴图值。1return tex2D(_MetallicMap, i.uv.xy).a * _S用这个新函数在MyFragmentProgram中得到光滑度。12345678910float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
return UNITY_BRDF_PBS(
albedo, specularTint,
oneMinusReflectivity, GetSmoothness(i),
i.normal, viewDir,
CreateLight(i), CreateIndirectLight(i, viewDir)
但是那不是我们唯一使用光滑度的地方,CreateIndirectLight也会使用光滑度。我们可以向这个函数中加入一个光滑度参数,但是我们也可以再调用一次GetSmoothness,着色器编译器会检测重复代码,并且优化。123456UnityIndirect CreateIndirectLight (Interpolators i, float3 viewDir) {
Unity_GlossyEnvironmentData envD
envData.roughness = 1 - GetSmoothness(i);
…}贴图的光滑度,强度最高那些沿着金属条边缘的方形物件是什么?
那些物件是由于法线贴图的DXT5nm纹理压缩产生的。特别地,如果太瘦的窄脊与紫外线轴不是对齐的,就不能正确估测这些脊。电路中陡峭的对角线边缘是这种压缩最坏的情况,这种局限性在表面上会变得更清晰可见,这些表面是金属的而且非常光滑,否则的话,就不那么明显。使用未压缩的法线贴图合并光滑度和Albedo当你两个都需要时,把金属度和光滑度贴图合并入单一的纹理中很好。金属部分几乎总是比其它位的光滑度高,所以当你需要金属度贴图时,实际上你也总是需要一个光滑度贴图。但是你也有可能需要一个光滑度贴图,而不需要混合金属和非金属,在这种情况下,金属度贴图是没有用的。对于那些不需要金属度贴图的不透明材质来说,可以在Albedo贴图的阿尔法通道中储存光滑度,这种用法很普遍,标准着色器支持在金属度贴图或者Albedo贴图中打包光滑度,让我们也这么做。在关键词间转换就像标准着色器一样,我们需要加一个选项来选择我们GUI的光滑度源。尽管标准着色器只支持两个贴图之间的选择,但是我们可以不用管它,直接把uniform光滑性也作为第三选项包含进来。为了体现这些选项,在MyLightingShaderGUI中定义一个枚举类型。123enum SmoothnessSource {
Uniform, Albedo, Metallic}
当Albedo贴图也作为光滑度源使用时,我们会向材质中添加_SMOOTHNESS_ALBEDO关键字。当使用金属度源时,我们会加入_SMOOTHNESS_ALBEDO来代替。同时uniform选项不对应任何关键字。
标准着色器也使用了一个浮点属性来追踪某个材质用了什么选项,但是我们可以只针对关键字实现这一点。GUI可以通过检查启用了哪些关键字来确定当前选择,这可以通过.IsKeywordEnabled方法实现,我们将会创建一个简便的封装。123bool IsKeywordEnabled (string keyword) {
return target.IsKeywordEnabled(keyword);}
现在DoSmoothness可以找出已选材质当前的光滑度源。1234567891011void DoSmoothness () {
SmoothnessSource source = SmoothnessSource.U
if (IsKeywordEnabled("_SMOOTHNESS_ALBEDO")) {
source = SmoothnessSource.A
else if (IsKeywordEnabled("_SMOOTHNESS_METALLIC")) {
source = SmoothnessSource.M
为了显示选项,我们可以用.EnumPopup方法,另外增加缩进级别,来与标准着色器的布局相匹配。12345EditorGUI.indentLevel += 2;editor.ShaderProperty(slider, MakeLabel(slider));EditorGUI.indentLevel += 1;EditorGUILayout.EnumPopup(MakeLabel("Source"), source);EditorGUI.indentLevel -= 3;光滑度源弹出
EnumPopup是一个基本编辑器小工具,它可以为任何枚举创建弹出列表,它返回选择的值。如果用户没有选择一个新选项,返回值与原始选择相同,否则的话,值就不同。所以,要想知道选择了哪个选项,我们需要把值赋回给源变量。由于该方法是通用枚举类型的,我们需要把它强制转换成SmoothnessSource。123source = (SmoothnessSource)EditorGUILayout.EnumPopup(
MakeLabel("Source"), source);
如果有改变产生,我们用源变量来控制应该设置哪个关键字,假如有的话。1234567EditorGUI.BeginChangeCheck();source = (SmoothnessSource)EditorGUILayout.EnumPopup(
MakeLabel("Source"), source
);if (EditorGUI.EndChangeCheck()) {
SetKeyword("_SMOOTHNESS_ALBEDO", source == SmoothnessSource.Albedo);SetKeyword("_SMOOTHNESS_METALLIC", source == SmoothnessSource.Metallic);}来自金属度贴图的光滑度支持撤销
我们现在可以改变光滑度源,但是它还并不支持撤销和重做动作,因为我们正在使用基本小工具,我们必须手动表示我们进行了一个支持撤销的动作。这可以通过.RegisterPropertyChangeUndo方法实现,它有一个参数是描述标签。为这个方法也建立一个封装。123void RecordAction (string label) {
editor.RegisterPropertyChangeUndo(label);}RecordAction必须在我们要改变的部分之前调用,它会为原状态创建一个快照,这样一来撤销动作就可以恢复到原状态。 1234567if (EditorGUI.EndChangeCheck()) {
RecordAction("Smoothness Source");
SetKeyword("_SMOOTHNESS_ALBEDO", source == SmoothnessSource.Albedo);
SetKeyword(
"_SMOOTHNESS_METALLIC", source == SmoothnessSource.Metallic
);}光滑度变量
为了全部支持三个选项,加一个着色器特征,特征在没有关键字、_SMOOTHNESS_ALBEDO,和_SMOOTHNESS_METALLIC中选择。如之前,基本和附加通道都必须支持它。12#pragma shader_feature _METALLIC_MAP#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC
在 GetSmoothness中,首先从光滑度为1开始,之后检查是否选择了Albedo源,如果选择了, Albedo贴图替换1;否则的话,检查是否选择了金属度源,如果选择了,用金属度源取而代之。当然了,只有当材质确实使用了金属度贴图的时候这才合理,所以也要检查材质是否使用了金属度贴图。
在那之后,返回我们得到的任何光滑度值,乘上_Smoothness属性的值,如果我们最终的变体没有使用贴图,编译器会乘上1来进行优化。123456789float GetSmoothness (Interpolators i) {
float smoothness = 1;
#if defined(_SMOOTHNESS_ALBEDO)
smoothness = tex2D(_MainTex, i.uv.xy).a;
#elif defined(_SMOOTHNESS_METALLIC) && defined(_METALLIC_MAP)
smoothness = tex2D(_MetallicMap, i.uv.xy).a;
return smoothness * _S}熔岩材质这里是冷却熔岩印象的albedo及法线贴图。材质是非金属的,但是光滑度变化不定,所以光滑度值存在Albedo贴图的阿尔法通道中。 带有光滑度和法线的Albedo用这些贴图创建一个材质,光滑度的Albedo源选项。熔岩材质
当使用Albedo源时,结果是灰色硬块的光滑度会比红沟高得多。 使用Albedo阿尔法,Uniform和mapped发射表面
到目前为止,我们只处理过了那些反射灯光的材质,通过漫反射或是镜面反射。我们需要一个光源来看到这些表面,但是也有一些表面自己会发射光亮。比如说:当一些东西变得足够热了,它就会开始发光,而你不需要一个不同的光源来看到它。标准着色器通过发射贴图和颜色支持这一点,我们也会支持。Mapped 和 Uniform
向我们的着色器中添加发射贴图和颜色的属性。两者都应该默认是黑色的,这意味着不发射任何光。因为我们只关心RGB通道,我们可以省略默认颜色的第四组件。12[NoScaleOffset] _EmissionMap ("Emission", 2D) = "black" {}_Emission ("Emission", Color) = (0, 0, 0)
许多材质没有发射贴图,所以,我们用着色器特征来创建需要和不需要发射贴图的变体,因为我们只需要加发射光一次,只在基本通道中包含特征。123#pragma shader_feature _METALLIC_MAP#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC#pragma shader_feature _EMISSION_MAP
向include文件中加入所需的采样器和浮点变量。12sampler2D _EmissionMfloat3 _E创建一个GetEmission函数来检索发射的颜色,假如有的话。当存在贴图时,取样颜色并且乘上uniform颜色;否则的话,就只返回uniform颜色。但是只需在基本通道中做这件麻烦事,在其它所有情况下,发射都是0,编译器会自动优化。1234567891011float3 GetEmission (Interpolators i) {
#if defined(FORWARD_BASE_PASS)
#if defined(_EMISSION_MAP)
return tex2D(_EmissionMap, i.uv.xy) * _E
#endif}由于发射自己来源于对象,它是独立于反射光的,所以就把它加到final颜色中吧。123456789101112float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
float4 color = UNITY_BRDF_PBS(
albedo, specularTint,
oneMinusReflectivity, GetSmoothness(i),
i.normal, viewDir,
CreateLight(i), CreateIndirectLight(i, viewDir)
color.rgb += GetEmission(i);
return }向GUI添加发射在MyLightingShaderGUI内部添加一个DoEmission方法,做这件事最快的方法是拷贝DoMetallic,再做一些改变。12345678910void DoEmission () {
MaterialProperty map = FindProperty("_EmissionMap");
EditorGUI.BeginChangeCheck();
editor.TexturePropertySingleLine(
MakeLabel(map, "Emission (RGB)"), map, FindProperty("_Emission")
if (EditorGUI.EndChangeCheck()) {
SetKeyword("_EMISSION_MAP", map.textureValue);
}}把它包含进入主要贴图部分。123456void DoMain () {
DoNormals();
DoEmission();
editor.TextureScaleOffsetProperty(mainTex);}带有发射贴图和颜色的监视器HDR发射
标准着色器不使用发射的常规颜色,不同的是,它支持高动态区域颜色。这意味着颜色的RGB组件可以高于1,这样一来,你可以表现出非常明亮的颜色。我们能看到亮度大于1的颜色吗?
在现实生活中,轰炸你的光子的数量没有限制。太阳也非常明亮,也很炫目。但是,计算机显示是有限的,你不能比1更高,有多亮取决于显示的亮度。
要在一面有意义的墙上使用HDR颜色,你必须实现色调映射。这意味着你将颜色从一个范围转换到一个范围。我们会在未来的教程中深入研究色调映射。HDR颜色也经常用于创建闪亮效果。
因为颜色属性是浮点向量,我们的值不止限于0到1。但是,标准颜色小工具的值本身是0到1。幸运的是,包括TexturePropert

我要回帖

更多关于 oc渲染器不支持a卡 的文章

 

随机推荐