Advanced ShaderLab topics

130 篇文章 5 订阅
57 篇文章 5 订阅

Implementing Fixed Function TexGen in Shaders

Before Unity 5, texture properties could have options inside thecurly brace block, e.g. TexGen CubeReflect. These were controlling fixed functiontexture coordinate generation. This functionality was removed in Unity 5.0; if you needtexgen you should write a vertex shader instead.

This page shows how to implement each of fixed function TexGen modes from Unity 4.

Cubemap reflection (TexGen CubeReflect)

TexGen CubeReflect is typically used for simple cubemap reflections.It reflects view direction along the normal in view space, and uses that as the UVcoordinate.

Shader "TexGen/CubeReflect" {
Properties {
    _Cube ("Cubemap", Cube) = "" { /* used to be TexGen CubeReflect */ }
}
SubShader { 
    Pass { 
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #include "UnityCG.cginc"
        
        struct v2f {
            float4 pos : SV_POSITION;
            float3 uv : TEXCOORD0;
        };

        v2f vert (float4 v : POSITION, float3 n : NORMAL)
        {
            v2f o;
            o.pos = mul(UNITY_MATRIX_MVP, v);

            // TexGen CubeReflect:
            // reflect view direction along the normal,
            // in view space
            float3 viewDir = normalize(ObjSpaceViewDir(v));
            o.uv = reflect(-viewDir, n);
            o.uv = mul(UNITY_MATRIX_MV, float4(o.uv,0));
            return o;
        }

        samplerCUBE _Cube;
        half4 frag (v2f i) : SV_Target
        {
            return texCUBE(_Cube, i.uv);
        }
        ENDCG 
    } 
}
}

Cubemap normal (TexGen CubeNormal)

TexGen CubeNormal is typically used with cubemaps too.It uses view space normal as the UV coordinate.

Shader "TexGen/CubeNormal" {
Properties {
    _Cube ("Cubemap", Cube) = "" { /* used to be TexGen CubeNormal */ }
}
SubShader { 
    Pass { 
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #include "UnityCG.cginc"
        
        struct v2f {
            float4 pos : SV_POSITION;
            float3 uv : TEXCOORD0;
        };

        v2f vert (float4 v : POSITION, float3 n : NORMAL)
        {
            v2f o;
            o.pos = mul(UNITY_MATRIX_MVP, v);

            // TexGen CubeNormal:
            // use view space normal of the object
            o.uv = mul((float3x3)UNITY_MATRIX_IT_MV, n);
            return o;
        }

        samplerCUBE _Cube;
        half4 frag (v2f i) : SV_Target
        {
            return texCUBE(_Cube, i.uv);
        }
        ENDCG 
    } 
}
}

Object space coordinates (TexGen ObjectLinear)

TexGen ObjectLinear used object space vertex position as UV coordinate.

Shader "TexGen/ObjectLinear" {
Properties {
    _MainTex ("Texture", 2D) = "" { /* used to be TexGen ObjectLinear */ }
}
SubShader { 
    Pass { 
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #include "UnityCG.cginc"
        
        struct v2f {
            float4 pos : SV_POSITION;
            float3 uv : TEXCOORD0;
        };

        v2f vert (float4 v : POSITION)
        {
            v2f o;
            o.pos = mul(UNITY_MATRIX_MVP, v);

            // TexGen ObjectLinear:
            // use object space vertex position
            o.uv = v.xyz;
            return o;
        }

        sampler2D _MainTex;
        half4 frag (v2f i) : SV_Target
        {
            return tex2D(_MainTex, i.uv.xy);
        }
        ENDCG 
    } 
}
}

View space coordinates (TexGen EyeLinear)

TexGen EyeLinear used view space vertex position as UV coordinate.

Shader "TexGen/EyeLinear" {
Properties {
    _MainTex ("Texture", 2D) = "" { /* used to be TexGen EyeLinear */ }
}
SubShader { 
    Pass { 
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #include "UnityCG.cginc"
        
        struct v2f {
            float4 pos : SV_POSITION;
            float3 uv : TEXCOORD0;
        };

        v2f vert (float4 v : POSITION)
        {
            v2f o;
            o.pos = mul(UNITY_MATRIX_MVP, v);

            // TexGen EyeLinear:
            // use view space vertex position
            o.uv = mul(UNITY_MATRIX_MV, v).xyz;
            return o;
        }

        sampler2D _MainTex;
        half4 frag (v2f i) : SV_Target
        {
            return tex2D(_MainTex, i.uv.xy);
        }
        ENDCG 
    } 
}
}

Spherical environment mapping (TexGen SphereMap)

TexGen SphereMap computes UV coordinates for spherical environment mapping.See OpenGL TexGen reference for the formula.

Shader "TexGen/SphereMap" {
Properties {
    _MainTex ("Texture", 2D) = "" { /* used to be TexGen SphereMap */ }
}
SubShader { 
    Pass { 
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #include "UnityCG.cginc"
        
        struct v2f {
            float4 pos : SV_POSITION;
            float2 uv : TEXCOORD0;
        };

        v2f vert (float4 v : POSITION, float3 n : NORMAL)
        {
            v2f o;
            o.pos = mul(UNITY_MATRIX_MVP, v);

            // TexGen SphereMap
            float3 viewDir = normalize(ObjSpaceViewDir(v));
            float3 r = reflect(-viewDir, n);
            r = mul((float3x3)UNITY_MATRIX_MV, r);
            r.z += 1;
            float m = 2 * length(r);
            o.uv = r.xy / m + 0.5;

            return o;
        }

        sampler2D _MainTex;
        half4 frag (v2f i) : SV_Target
        {
            return tex2D(_MainTex, i.uv);
        }
        ENDCG 
    } 
}
}

Shader Level of Detail

Shader Level of Detail (LOD) works by only using shaders or subshaders that have their LOD value less than a given number.

By default, allowed LOD level is infinite, that is, all shaders that are supported by the user’s hardware can be used. However, in some cases you might want to drop shader details, even if the hardware can support them. For example, some cheap graphics cards might support all the features, but are too slow to use them. So you may want to not use parallax normal mapping on them.

Shader LOD can be either set per individual shader (using Shader.maximumLOD), or globally for all shaders (using Shader.globalMaximumLOD).

In your custom shaders, use LOD command to set up LOD value for any subshader.

Built-in shaders in Unity have their LODs set up this way:

  • VertexLit kind of shaders = 100
  • Decal, Reflective VertexLit = 150
  • Diffuse = 200
  • Diffuse Detail, Reflective Bumped Unlit, Reflective Bumped VertexLit = 250
  • Bumped, Specular = 300
  • Bumped Specular = 400
  • Parallax = 500
  • Parallax Specular = 600

Platform Specific Rendering Differences

Unity runs on various platforms and in some cases there are differences in how things behave. Most of the time Unity hides the differences from you, but sometimes you can still bump into them.

Render Texture Coordinates

Vertical texture coordinate conventions differ between Direct3D-like and OpenGL-like platforms:

  • In Direct3D and Metal, the coordinate is zero at the top, and increases downwards.
  • In OpenGL and OpenGL ES, the coordinate is zero at the bottom, and increases upwards.

Most of the time this does not really matter, except when rendering into a Render Texture. In that case, Unity internally flips rendering upside down when rendering into a texture on Direct3D, so that the conventions match between the platforms.

One case where this does not happen, is when Image Effects and Anti-Aliasing is used. In this case, Unity renders to screen to get anti-aliasing, and then “resolves” rendering into a RenderTexture for further processing with an Image Effect. The resulting source texture for an image effect is not flipped upside down on Direct3D (unlike all other Render Textures).

If your Image Effect is a simple one (processes one texture at a time) then this does not really matter because Graphics.Blit takes care of that.

However, if you’re processing more than one RenderTexture together in your Image Effect, most likely they will come out at different vertical orientations (only in Direct3D-like platforms, and only when anti-aliasing is used). You need to manually “flip” the screen texture upside down in your vertex shader, like this:

// On D3D when AA is used, the main texture and scene depth texture
// will come out in different vertical orientations.
// So flip sampling of the texture when that is the case (main texture
// texel size will have negative Y).

#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
        uv.y = 1-uv.y;
#endif

Check out the Edge Detection scene in the Shader Replacement sample project for an example of this. Edge detection there uses both the screen texture and the Camera’s Depth+Normals texture.

Semantics used by shaders

To get shaders working on all platforms, some special shader values should use these semantics:

  • Vertex shader output (clip space) position: SV_POSITION. Sometimes shaders use POSITION semantics for that, but this will not work on Sony PS4 and will not work when tessellation is used.
  • Fragment shader output color: SV_Target. Sometimes shaders use COLOR or COLOR0 for that, but again that will not work on PS4.
  • When rendering meshes as Points, make sure to output PSIZE semantics output from the vertex shader (e.g. set it to 1). Some platforms (e.g. OpenGL ES or Metal) treat point size as “undefined” when it’s not written to from the shader.

AlphaTest and programmable shaders

Some platforms, most notably mobile (OpenGL ES & Metal) and Direct3D 11, do not have fixed function alpha testing functionality. When you are using programmable shaders, it’s advisable to use the Cg/HLSL clip() function in the pixel shader instead.

Direct3D 9 / 11 shader compiler is more picky about syntax

Direct3D platforms use Microsoft’s HLSL shader compiler. The HLSL compiler is more picky than other compilers about various subtle shader errors. For example, it won’t accept function output values that aren’t initialized properly.

The most common places where you would run into this are:

  • A Surface shader vertex modifier that has an “out” parameter. Make sure to initialize the output like this: void vert (inout appdata_full v, out Input o) { UNITY_INITIALIZE_OUTPUT(Input,o); // … }

  • Partially initialized values, e.g. a function returns float4, but the code only sets the .xyz values of it. Make sure to set all values or else change to float3 if you only need three values.

  • Using tex2D in the vertex shader. This is not valid since UV derivatives don’t exist in the vertex shader; you need to sample an explicit mip level instead, e.g. use tex2Dlod (tex, float4(uv,0,0)). You’ll need to add #pragma target 3.0 since tex2Dlod is a shader model 3.0 feature.

DirectX 11 HLSL syntax and Surface Shaders

Currently some parts of the surface shader compilation pipeline do not understand DX11-specific HLSL syntax. If you’re using HLSL features like StructuredBuffers, RWTextures and other non-DX9 syntax, you have to wrap them in a DX11-only preprocessor macro:

#ifdef SHADER_API_D3D11
// DX11-specific code, e.g.
StructuredBuffer<float4> myColors;
RWTexture2D<float4> myRandomWriteTexture;
#endif

Using Shader Framebuffer Fetch

Some GPUs (most notably PowerVR based ones on iOS) allow doing a form of programmable blending, by providing current fragment color as input to the fragment shader (see EXT_shader_framebuffer_fetch).

It is possible to write shaders in Unity that use the framebuffer fetch functionality. When writing HLSL/Cg fragment shader, just use inout color argument in it, for example:

CGPROGRAM
// only compile shader for platforms that can potentially
// do it (currently gles,gles3,metal)
#pragma only_renderers framebufferfetch

void frag (v2f i, inout half4 ocol : SV_Target)
{
    // ocol can be read (current framebuffer color)
    // and written into (will change color to that one)
    // ...
}   
ENDCG

iPad2 and MSAA and alpha-blended geometry

There is a bug in Apple driver resulting in artifacts when MSAA is enabled and alpha-blended geometry is drawn with non RGBA colorMask. To prevent artifacts we force RGBA colorMask when this configuration is encountered, though it will render built-in Glow FX unusable (as it needs DST_ALPHA for intensity value). Also, please update your shaders if you wrote them yourself (see “Render Setup -> ColorMask” in Pass Docs).

Performance Tips when Writing Shaders

Use Common sense ;)

Compute only things that you need; anything that is not actually needed can be eliminated. For example, supporting per-material color is nice to make a shader more flexible, but if you always leave that color set to white then it’s useless computations performed for each vertex or pixel rendered on screen.

Another thing to keep in mind is frequency of computations. Usually there are many more pixels rendered (hence their pixel shaders executed) than there are vertices (vertex shader executions); and more vertices than objects being rendered. So generally if you can, move computations out of pixel shader into the vertex shader; or out of shaders completely and set the values once from a script.

Less Generic Surface Shaders

Surface Shaders are great for writing shaders that interact with lighting. However, their default options are tuned for “general case”. In many cases, you can tweak them to make shaders run faster or at least be smaller:

  • approxview directive for shaders that use view direction (i.e. Specular) will make view direction be normalized per-vertex instead of per-pixel. This is approximate, but often good enough.
  • halfasview for Specular shader types is even faster. Half-vector (halfway between lighting direction and view vector) will be computed and normalized per vertex, and lighting function will already receive half-vector as a parameter instead of view vector.
  • noforwardadd will make a shader fully support only one directional light in Forward rendering. The rest of the lights can still have an effect as per-vertex lights or spherical harmonics. This is great to make shader smaller and make sure it always renders in one pass, even with multiple lights present.
  • noambient will disable ambient lighting and spherical harmonics lights on a shader. This can be slightly faster.

Precision of computations

When writing shaders in Cg/HLSL, there are three basic number types: float, half and fixed (as well as vector/matrix variants of them, e.g. half3 and float4x4):

  • float: high precision floating point. Generally 32 bits, just like float type in regular programming languages.
  • half: medium precision floating point. Generally 16 bits, with a range of –60000 to +60000 and 3.3 decimal digits of precision.
  • fixed: low precision fixed point. Generally 11 bits, with a range of –2.0 to +2.0 and 1/256th precision.

Use lowest precision that is possible; this is especially important on mobile platforms like iOS and Android. Good rules of thumb are:

  • For colors and unit length vectors, use fixed.
  • For others, use half if range and precision is fine; otherwise use float.

On mobile platforms, the key is to ensure as much as possible stays in low precision in the fragment shader. On most mobile GPUs, applying swizzles to low precision (fixed/lowp) types is costly; converting between fixed/lowp and higher precision types is quite costly as well.

In practice, what exactly type will be used for float/half/fixed depends on the platform and the GPU. General summary:

  • All modern desktop GPUs will always compute everything in full floating point precision; float/half/fixed end up being exactly the same underneath. Which makes testing a bit tricky, since on PC it’s hard to see if half/fixed precision is really enough. Make sure to test your shaders on actual mobile device!
  • Mobile GPUs have actual half-precision support, and it is often both faster and uses less power to do calculations.
  • “Fixed” precision is generally only useful for older mobile GPUs; most modern GPUs (the ones that can run OpenGL ES 3 or Metal) internally treat fixed and half exactly the same.

Alpha Testing

Fixed function AlphaTest or it’s programmable equivalent, clip(), has different performance characteristics on different platforms:

  • Generally it’s a small advantage to use it to cull out totally transparent pixels on most platforms.
  • However, on PowerVR GPUs found in iOS and some Android devices, alpha testing is expensive. Do not try to use it as “performance optimization” there, it will be slower.

Color Mask

On some platforms (mostly mobile GPUs found in iOS and Android devices), using ColorMask to leave out some channels (e.g. ColorMask RGB) can be expensive, so only use it if really necessary.

Unity’s Rendering Pipeline

Shaders define both how an object looks by itself (its material properties) and how it reacts to the light. Because lighting calculations must be built into the shader, and there are many possible light & shadow types, writing quality shaders that “just work” would be an involved task. To make it easier, Unity has Surface Shaders, where all the lighting, shadowing, lightmapping, forward vs. deferred rendering things are taken care of automatically.

This document describes the pecularities of Unity’s lighting & rendering pipeline and what happens behind the scenes of Surface Shaders.

Rendering Paths

How lighting is applied and which Passes of the shader are used depends on which Rendering Path is used. Each pass in a shader communicates its lighting type via Pass Tags.

Forward Rendering path

ForwardBase pass renders ambient, lightmaps, main directional light and not important (vertex/SH) lights at once. ForwardAdd pass is used for any additive per-pixel lights; one invocation per object illuminated by such light is done. See Forward Rendering for details.

If forward rendering is used, but a shader does not have forward-suitable passes (i.e. neither ForwardBase nor ForwardAdd pass types are present), then that object is rendered just like it would in Vertex Lit path, see below.

Deferred Shading path

Deferred pass renders all information needed for lighting (in built-in shaders: diffuse color, specular color, smoothness,world space normal, emission). It also adds lightmaps, reflection probes and ambient lighting into the emission channel. See Deferred Shading for details.

Legacy Deferred Lighting path

PrepassBase pass renders normals & specular exponent; PrepassFinal pass renders final color by combining textures, lighting & emissive material properties. All regular in-scene lighting is done separately in screen-space. See Deferred Lighting for details.

Legacy Vertex Lit Rendering path

Since vertex lighting is most often used on platforms that do not support programmable shaders, Unity can’t create multiple shader variants internally to handle lightmapped vs. non-lightmapped cases. So to handle lightmapped and non-lightmapped objects, multiple passes have to be written explicitly.

  • Vertex pass is used for non-lightmapped objects. All lights are rendered at once, using a fixed function OpenGL/Direct3D lighting model (Blinn-Phong)
  • VertexLMRGBM pass is used for lightmapped objects, when lightmaps are RGBM encoded (PC and consoles). No realtime lighting is applied; pass is expected to combine textures with a lightmap.
  • VertexLMM pass is used for lightmapped objects, when lightmaps are double-LDR encoded (mobile platforms). No realtime lighting is applied; pass is expected to combine textures with a lightmap.

See Also

Rendering Paths


Unity supports different Rendering Paths. You should choose which one you use depending on your game content and target platform / hardware. Different rendering paths have different performance characteristics that mostly affect Lights and Shadows. See render pipeline for technical details.

The rendering Path used by your project is chosen in Player Settings. Additionally, you can override it for each Camera.

If the graphics card can’t handle a selected rendering path, Unity will automatically use a lower fidelity one. For example, on a GPU that can’t handle Deferred Shading, Forward Rendering will be used.

Deferred Shading

Deferred Shading is the rendering path with the most lighting and shadow fidelity, and is best suited if you have many realtime lights. It requires a certain level of hardware support.

For more details see the Deferred Shading page.

Forward Rendering

Forward is the traditional rendering path. It supports all the typical Unity graphics features (normal maps, per-pixel lights, shadows etc.). However under default settings, only a small number of the brightest lights are rendered in per-pixel lighting mode. The rest of the lights are calculated at object vertices or per-object.

For more details see the Forward Rendering page.

Legacy Deferred

Legacy Deferred (light prepass) is similar to Deferred Shading, just using a different technique with different trade-offs. It does not support the Unity 5 physically based standard shader.

For more details see the Deferred Lighting page.

Legacy Vertex Lit

Legacy Vertex Lit is the rendering path with the lowest lighting fidelity and no support for realtime shadows. It is a subset of Forward rendering path.

For more details see the Vertex Lit page.

Rendering Paths Comparison

 DeferredForwardLegacy DeferredVertex Lit
Features    
Per-pixel lighting (normal maps, light cookies)YesYesYes-
Realtime shadowsYesWith caveatsYes-
Reflection ProbesYesYes--
Depth&Normals BuffersYesAdditional render passesYes-
Soft ParticlesYes-Yes-
Semitransparent objects-Yes-Yes
Anti-Aliasing-Yes-Yes
Light Culling MasksLimitedYesLimitedYes
Lighting FidelityAll per-pixelSome per-pixelAll per-pixelAll per-vertex
Performance    
Cost of a per-pixel LightNumber of pixels it illuminatesNumber of pixels * Number of objects it illuminatesNumber of pixels it illuminates-
Number of times objects are normally rendered1Number of per-pixel lights21
Overhead for simple scenesHighNoneMediumNone
Platform Support    
PC (Windows/Mac)Shader Model 3.0+ & MRTAllShader Model 3.0+All
Mobile (iOS/Android)OpenGL ES 3.0 & MRTAllOpenGL ES 2.0All
ConsolesXB1, PS4AllXB1, PS4, 360, PS3-

















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值