Skip to content

第9章第2节疑似代码错误 #35

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
JOKULLIU opened this issue Aug 30, 2016 · 6 comments
Closed

第9章第2节疑似代码错误 #35

JOKULLIU opened this issue Aug 30, 2016 · 6 comments

Comments

@JOKULLIU
Copy link

作者你好。
前次给你发了一个邮件说发布PC包的Shader不被支持的问题。
根据我逐个计算模块的排查,以及直接使用你的工程里面第9章第2节的“Forward Rendering”的源码,基本可以确定是以下代码引发错误:

#ifdef USING_DIRECTIONAL_LIGHT
    fixed atten = 1.0;
#else
    float3 lightCoord = mul(_LightMatrix0, float4(i.worldPos, 1)).xyz;
// half lr = dot(lightCoord, lightCoord);
    fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
// fixed atten = tex2D(_LightTexture0, half2(lr, lr)).UNITY_ATTEN_CHANNEL;
#endif

上面注释的两句代码,是我误以为d3d11不支持.rr的写法,就换了一个写法,但不管用。
报错是:
Shader error in 'Forward Rendering': 'tex2D': no matching 2 parameter intrinsic function; Possible intrinsic functions are:……
后面是Unity给出的tex2D函数格式建议。
此错误在编辑器模式下不会报,且可以正确计算衰减,但如果打包至win平台,则会报上述错误。
如将此段代码注释,一切正常。
此错误会导致Shader无法被执行,两个Pass被移除,并寻找备胎。
备胎代码:
FallBack "Specular"
无法正常执行。
下面是Unity的output_log记录:

WARNING: Shader Unsupported: 'Forward Rendering' - Pass '' has no vertex shader
WARNING: Shader Unsupported: 'Forward Rendering' - All passes removed
@candycat1992
Copy link
Owner

亲爱滴你真是好学,其实这一节的确有需要完善的地方,第9章是我写的最最痛苦的一章,没有之一,就是因为Unity的光照计算基本是黑盒的,全靠猜和自己看代码。但问题是,要在一本入门书里面讲清楚原原本本怎么计算的很复杂,对初学者来说反而不易于打基础(我认为的……),而且我不敢保证我的分析一定对,于是这一节我就简化了……简……化……了……我本来是想引出后面9.4.4节,告诉大家,“嗯,自己处理很麻烦的哦,而且一不小心就错了哦,所以大家都来用自带的函数和宏计算吧!”但我没有写的这么明白……

其实这个报错当时我发现了,掩面(/ω╲)我错了……我再解释一下,之后想办法补充到书里面吧。


我们都知道Unity写shader处理一些光照很方便(当然前提是我们知道怎么处理。),但这显然不是免费的午餐,Unity在背后做了很多工作。在光照方面,一个最简单的问题就是,Unity凭什么知道当前的光源类型、有没有使用lightmap、有没有开启阴影这些条件呢? 想象一下我们自己写shader的话会怎么办,我们肯定会根据不同的条件来使用不同的shader,其实Unity也是这么做的,它依靠的就是shader program variants。shader program variants其实就是shader变种,简单说就是由一个Unity Shader可以衍生出了许多真正的shader program,我们会根据条件来判断到底把哪个shader program发送给GPU。那么,我们明明只写了一个Unity Shader啊,衍生出来的shader program哪里来的?这就是通过multi_compile指令。

还记得我们要求在Forward Base和Forward Add里面比如要加#pragma multi_compile_fwdbase或者#pragma multi_compile_fwdadd这样的代码吗?这些指令其实就是Unity预定义的multi_compile指令,一旦声明了这些指令,Unity就会为ForwardBase和ForwardAdd生成大量的shader program以及对应的keyword。如果你不理解的话,我们可以举个例子。你看我们的代码里经常会通过#ifdef来判断一些状态有没有开启。我们随便粘贴AutoLight.cginc最开头的一段代码:

// ---- Screen space shadows
#if defined (SHADOWS_SCREEN)


#define SHADOW_COORDS(idx1) unityShadowCoord4 _ShadowCoord : TEXCOORD##idx1;

#if defined(UNITY_NO_SCREENSPACE_SHADOWS)

UNITY_DECLARE_SHADOWMAP(_ShadowMapTexture);
#define TRANSFER_SHADOW(a) a._ShadowCoord = mul( unity_World2Shadow[0], mul( _Object2World, v.vertex ) );

inline fixed unitySampleShadow (unityShadowCoord4 shadowCoord)
{
    #if defined(SHADOWS_NATIVE)

    fixed shadow = UNITY_SAMPLE_SHADOW(_ShadowMapTexture, shadowCoord.xyz);
    shadow = _LightShadowData.r + shadow * (1-_LightShadowData.r);
    return shadow;

    #else

    unityShadowCoord dist = SAMPLE_DEPTH_TEXTURE_PROJ(_ShadowMapTexture, shadowCoord);

    // tegra is confused if we use _LightShadowData.x directly
    // with "ambiguous overloaded function reference max(mediump float, float)"
    half lightShadowDataX = _LightShadowData.x;
    return max(dist > (shadowCoord.z/shadowCoord.w), lightShadowDataX);

    #endif
}

#else // UNITY_NO_SCREENSPACE_SHADOWS

sampler2D _ShadowMapTexture;
#define TRANSFER_SHADOW(a) a._ShadowCoord = ComputeScreenPos(a.pos);

inline fixed unitySampleShadow (unityShadowCoord4 shadowCoord)
{
    fixed shadow = tex2Dproj( _ShadowMapTexture, UNITY_PROJ_COORD(shadowCoord) ).r;
    return shadow;
}

#endif

#define SHADOW_ATTENUATION(a) unitySampleShadow(a._ShadowCoord)

#endif

上面的SHADOWS_SCREEN、UNITY_NO_SCREENSPACE_SHADOWS、SHADOWS_NATIVE等这些都是Unity生成的keyword(对,就在你写下#pragma multi_compile_fwdbase这些话的时候Unity背后就干了这么多的事情。),Unity靠这些keyword来判断当前使用哪些代码。而且这不同于分支语句,Unity其实是真的生成了非常多的shader program,这你可以通过Unity Shader导入面板的Compile and show code来看到,比如我们粘贴一段:

SubProgram "gles3 " {
Keywords { "DIRECTIONAL" "SHADOWS_SCREEN" "SHADOWS_NATIVE" "LIGHTMAP_OFF" "DIRLIGHTMAP_OFF" "DYNAMICLIGHTMAP_OFF" }
"#ifdef VERTEX
#version 300 es
precision highp float;
precision highp int;
uniform     vec4 _Time;
uniform     vec4 _SinTime;
uniform     vec4 _CosTime;
uniform     vec4 unity_DeltaTime;
uniform     vec3 _WorldSpaceCameraPos;
uniform     vec4 _ProjectionParams;
uniform     vec4 _ScreenParams;
uniform     vec4 _ZBufferParams;
uniform     vec4 unity_OrthoParams;
uniform     vec4 unity_CameraWorldClipPlanes[6];
uniform     mat4 unity_CameraProjection;
uniform     mat4 unity_CameraInvProjection;
uniform     mediump vec4 _WorldSpaceLightPos0;
uniform     vec4 _LightPositionRange;
uniform     vec4 unity_4LightPosX0;
uniform     vec4 unity_4LightPosY0;
uniform     vec4 unity_4LightPosZ0;
uniform     mediump vec4 unity_4LightAtten0;
uniform     mediump vec4 unity_LightColor[8];
uniform     vec4 unity_LightPosition[8];
uniform     mediump vec4 unity_LightAtten[8];
uniform     vec4 unity_SpotDirection[8];
uniform     mediump vec4 unity_SHAr;
uniform     mediump vec4 unity_SHAg;
uniform     mediump vec4 unity_SHAb;
uniform     mediump vec4 unity_SHBr;
uniform     mediump vec4 unity_SHBg;
uniform     mediump vec4 unity_SHBb;
...

后面太长我就不粘贴了。这个shader program就对应了"DIRECTIONAL" "SHADOWS_SCREEN" "SHADOWS_NATIVE" "LIGHTMAP_OFF" "DIRLIGHTMAP_OFF" "DYNAMICLIGHTMAP_OFF"这些keyword开启时的代码。

当然,我们自己也可以定义keyword,来生成自己的shader program变种,可以参考我的博客


说了这么多,不知道你有没有晕掉。其实上面就是想说明,Unity是如何来处理不同的光照条件、执行不同的代码的。嗯,就是shader program variants + 很多的keyword。那么回到我们的问题,为什么书里面的这个代码报错了。

答案就是我们的keyword判断条件有问题,导致_LightTexture0这张纹理并没有被定义。你可以将原来的代码替换成下面的代码:

#ifdef USING_DIRECTIONAL_LIGHT
    fixed atten = 1.0;
#else
    #if defined (POINT)
        float3 lightCoord = mul(_LightMatrix0, float4(i.worldPos, 1)).xyz;
        fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
    #elif defined (SPOT)
        float4 lightCoord = mul(_LightMatrix0, float4(i.worldPos, 1));
        fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
    #else
        fixed atten = 1.0;
    #endif
#endif

应该就不会再出错了。你对比就会发现,其实就是更加精确的判断了keyword条件,保证只在正确的时候执行那段访问纹理的代码。_LightTexture0这张纹理只在某些条件下会被定义,例如在开启了POINT、SPOT、POINT_COOKIE、DIRECTIONAL_COOKIE等,具体你可以在AutoLight.cginc里面找到。但因为原来的代码里没有进行严格判断,在发布的时候由于Unity会严格编译Unity Shader根据不同的keyword生成对应的shader program,此时它就会发现我们的错误了。那么为什么编辑器里没有报错呢?大概是Unity编辑器状态下并没有严格编译(这段是我自己猜的哈……)


最后我再补充下,书里面的这些代码都是怎么写出来,可不是我自己歪歪写的。这都是跑到自带的cginc文件里面,一行行看它们的代码是怎么写的,然后删减掉不必要的部分。如果以后有遇到类似问题,可以也直接看它们的逻辑和判断是怎么写的,尤其是遇到这些keyword的时候!

我啰嗦了这么多,是不是可以理解我为什么没有把这一段放到书里 (⊙﹏⊙)b

@JOKULLIU
Copy link
Author

JOKULLIU commented Aug 31, 2016

小脸一红=^^=
我后来猜到是_LightTexture0出问题了,但我怀疑是缺少类似阴影的声明寄存器的代码,你看,基础功不扎实,对底层了解不透彻,就像无头苍蝇一样东撞西碰的……

此外还有一处报错,我在网上找到解决的代码,这个错误也是只在发布的时候会报,编辑器模式下可能看不到(也可能会有黄色提示),总之加上后,所有红色的黄色的都没了(世界终于干净了……)
报错是:
Shader error in '***/***': variable 'o' used without having been completely initialized at line 73 (on d3d9)
我把名字匿了,反正没啥用,就是说在顶点函数中,o没有被初始化。
改进如下:

v2f vert (a2v v)
{
        v2f o;
        UNITY_INITIALIZE_OUTPUT(v2f, o);
        o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
        ..........
}

加上UNITY_INITIALIZE_OUTPUT宏就对了。
这个宏出自HLSLSupport.cginc 文件,里面的定义是:

// Initialize arbitrary structure with zero values.
// Not supported on some backends (e.g. Cg-based like PS3 and particularly with nested structs).
// hlsl2glsl would almost support it, except with structs that have arrays -- so treat as not supported there either :(
#if defined(UNITY_COMPILER_HLSL) || defined(SHADER_API_PSSL) || defined(SHADER_API_GLES3) || defined(SHADER_API_GLCORE)
#define UNITY_INITIALIZE_OUTPUT(type,name) name = (type)0;
#else
#define UNITY_INITIALIZE_OUTPUT(type,name)
#endif

上面的注释大约就是告诉你初始化值,并且在……情况下不被支持(E文差就是这种结局)。
然后欣赏一下Unity工程师的卖萌小情节(他们真的好喜欢卖萌,尼玛有这个休闲时间,还不如好好整理下cginc的文档!)。
但有两点想不通:
1、凡是只有1个Pass的Shader,是不会报初始化错误的,Why?
2、凡是有2个Pass的,只需要在BasePass中添加即可,AddPass不需要,Why?

@candycat1992
Copy link
Owner

这个错误很常见,我在5.6.2节提到过这个错误:

qq 20160831130456

具体原因和解决方法你可以再读一下这一小节。其实解决方法很简单,就是保证声明的每个变量都被赋值了就好了。

另外,这个错误是我书中的某个代码吗?我记得我都检查过,应该不会有这种错误才对啊。

@JOKULLIU
Copy link
Author

JOKULLIU commented Sep 1, 2016

我的代码在报这个错。主要是因为单个Pass的Shader都不会报错,2个Pass就报错,让人觉得比较难理解。
书上的一些小细节特别容易被看漏,读书马大哈就是指像我这样的人。-_-!

@candycat1992
Copy link
Owner

额这种还是因为自己写代码不严谨造成的,无论几个Pass还是都应该初始化所有变量。

哈哈书里面有很多那种第一次看起来不重要、第二次看就发现很重要的点……

@jonnyOntheroad
Copy link

问个问题, 这句话如何理解的: max(dist > (shadowCoord.z/shadowCoord.w), lightShadowDataX);
第一个参数: dist > (shadowCoord.z/shadowCoord.w)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants