Code Monkey home page Code Monkey logo

unity_shaders_book's Introduction

《Unity Shader入门精要》源代码

本项目是书籍《Unity Shader入门精要》的配套源代码。最新版本请移步本项目的Github页面

源码下载

我们推荐您从Github上clone项目源码并及时检查更新。如果下载速度过慢可以移步百度网盘地址

纸质版书籍可在以下链接购买:亚马逊当当京东

随书彩图

我们提供了包含书中所有插图的彩色版插图集锦:HTMLPDF

2019年新增:改版后的第十八章

当年乐观地以为自己有时间写第二版,是我太年轻了…… 第一版由于我能力有限的确有很多问题,感谢大家一直以来的支持。当年在我有时间的时候曾经花了一些时间重写了第18章,本来是想要放到第二版里的,但是目前第二版遥遥无期,索性直接把改版后的第18章直接放出来,希望能让读者有新的收获:改版后的第18章

第四章勘误

由于数学章是全书的重要基础,我们决定把第四章公开,来及时让读者获取最新的第四章数学章的内容:PDF

注意:我们可能会根据读者勘误随时更新该文档,内容和页号可能会与读者手中的版本不同,实体书中的勘误会在每次重印时进行修正。

读者反馈和勘误

尽管我们在本书的编写过程中多次检查内容的正确性,但不可避免书中仍然会出现一些错误,欢迎读者批评指正。任何关于本书内容、源码等方面的问题,欢迎读者反映到本书源码所在的Github讨论页,也可以发邮件([email protected])联系笔者。

关于目前已发现的错误我们会及时在网上更新:勘误列表

我们也维护了读者反馈的问题列表:FAQ

Unity版本

我们推荐使用Unity 5.0以上的版本来编译本项目。如果你打算使用更低版本的Unity,那么在学习本书时可能就会遇到一些问题

  • 你可能发现会有些菜单或变量在你的Unity中找不到,这可能就是由于Unity版本不同造成的。绝大多数情况下,本书的代码和指令仍然可以工作良好,但在一些特殊情况下,Unity可能会更改底层的实现细节,造成同样的代码会得到不一样的效果(例如,在非统一缩放时对法线进行变换)。

  • 还有一些问题是Unity提供的内置变量、宏和函数,例如我们在书中经常会使用UnityObjectToWorldNormal内置函数把法线从模型空间变换到世界空间中,但这个函数是在Unity 5中才被引入的,因此如果读者使用的是Unity 5之前的版本就会报错。类似的情况还有和阴影相关的宏和变量等。

  • 和Unity 4.x版本相比,Unity 5.x最大的变化之一就是很多以前只有在专业版才支持的功能,在免费版也同样提供了。因此,如果读者使用的是Unity 4.x免费版,可能会发现本书中的某些材质会出错。

Unity 5.3及其以下Unity 5.x版本

分支链接master

在本书编写时,我们使用的版本是Unity 5.3,因此使用这些Unity版本的读者请使用本项目master分支的相关代码。

Unity 5.4及其以上Unity 5.x版本

分支链接unity_5_4

Unity 5.4对Shader部分进行了一些比较大的更新,比较明显的变化有:

  • 使用了unity_XXX来代替原有的XXX变换矩阵,例如_Object2World被替换成了unity_ObjectToWorld,_World2Object被替换成了unity_WorldToObject(均在UnityShaderVariables.cginc文件中被声明),_LightMatrix0被替换成了unity_WorldToLight(在AutoLight.cginc文件中被声明)。

  • 使用了一些内置函数来代替某些运算,例如mul(UNITY_MATRIX_MVP,)相关计算被替换成了UnityObjectToClipPos()。

在学习本书时,读者需要注意代码中一些由于更新造成的变化。

升级Unity 5.5

从Unity 5.5开始,Unity在某些平台(如DX11、DX12、PS4、Xbox One、Metal)等平台对深度缓存进行了反转操作,使得在近平面处的深度值为1,而远平面处为0。这样做的原因主要是为了更加充分得利用浮点深度缓存,具体原因可以参见NVIDIA的相关博客Depth Precision Visualized。Unity在Upgrading to Unity 5.5Platform-specific rendering differences文档中对此进行了说明。

在本书代码中,我们在第13章用到了深度纹理,其中对于使用了Linear01Depth、LinearEyeDepth等Unity内置函数的部分不受此变化影响,但我们在Chapter13-MotionBlurWithDepthTexture中直接访问了深度值来计算世界空间下的坐标:

// Get the depth buffer value at this pixel.
float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth);

这使得在反转深度缓存的情况下会得到错误的结果。为了解决这个问题,我们可以使用内置宏来判断深度是否已被反转,并据此来做出相应的计算。变化后的代码如下:

// Get the depth buffer value at this pixel.
float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth);
#if defined(UNITY_REVERSED_Z)
	d = 1.0 - d;
#endif

截止到目前的Unity 2017版本

分支链接unity_2017_1

Unity 2017对Shader部分没有较大更新,我们主要做了以下更改来消除升级造成的Shader Warning信息:

  • 使用内置的UnityObjectToViewPos(*)函数来代替mul(UNITY_MATRIX_MV, *)对顶点进行变换。

在学习本书时,读者需要注意代码中一些由于更新造成的变化。

使用说明

本书源码的组织方式大多按资源类型和章节进行划分,主要包含了以下关键文件夹:

文件夹 说明
Assets/Scenes 包含了各章对应的场景,每个章节对应一个子文件夹,例如第七章所有场景所在的子文件夹为Assets/Scenes/Chapter7。每个场景的命名方式为Scene_章号_小节号_次小节号,例如7.2.3节对应的场景名为Scene_7_2_3。如果同一个小节包含了多个场景,那么会使用英文字母作为后缀依次表示,例如7.1.2节包含了两个场景Scene_7_1_2_a和Scene_7_1_2_b。
Assets/Shaders 包含了各章实现的Unity Shader文件,每个章节对应一个子文件夹,例如第七章实现的所有Unity Shader所在的子文件夹为Assets/Shaders/Chapter7。每个Unity Shader的命名方式为ChapterX-功能,例如第七章使用渐变纹理的Unity Shader名为Chapter7-RampTexture。
Assets/Materials 包含了各章对应的材质,每个章节对应一个子文件夹,例如第七章所有材质所在的子文件夹为Assets/Materials/Chapter7。每个材质的命名方式与它使用的Unity Shader名称相匹配,并以Mat作为后缀,例如使用名为Chapter7-RampTexture的Unity Shader的材质名称是RampTextureMat。
Assets/Scripts 包含了各章对应的C#脚本,每个章节对应一个子文件夹,例如第五章所有脚本所在的子文件夹为Assets/Scripts/Chapter5。
Assets/Textures 包含了各章使用的纹理贴图,每个章节对应一个子文件夹,例如第七章使用的所有纹理所在的子文件夹为Assets/Textures/Chapter7。

除了上述文件夹外,源码中还包含了一些辅助文件夹,例如Assets/Editor文件夹中包含了一些需要在编辑器状态下运行的脚本,Assets/Prefabs文件夹下包含了各章使用的预设模型和其他常用预设模型等。

unity_shaders_book's People

Contributors

candycat1992 avatar gary9716 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

unity_shaders_book's Issues

About RampDiffuse

There is a bug in RampDiffuse line 29.float2 cannot only one argument

【建议征集】第二版内容更新建议征集

由于第一次写书经验有限,第一版难免在内容编排上有所欠缺,因此希望可以在第二版(不是印刷版次)里面对此进行改善。这个issue会罗列在第二版可能会添加的内容,如果有其他建议可以在下方留言,合适的建议会更新到列表里。理论上效果实现方面建议不会放在这本入门书里面,例如实现角色穿透墙的效果等等,但如果效果的确对一些概念理解非常有用会考虑增添。再次感谢大家对书的支持和厚爱 :)


下面是目前考虑增加的内容:

  • 光照贴图(lightmap)
  • Light Probes和Reflection Probes
  • 对内置Shader光照的更加详细的解释和说明,例如SH光照等
  • Unity内置Shader的实现机制,这里指的是keyword和shader variants的相关内容
  • 内置雾效
  • 性能分析类工具的使用,如GPA等
  • Stencil Buffer

Page151 7.2.3(9)v.tangent.w

在原文中写到:“因为和切线与法线方向都垂直的方向有两个”
但是实际上,两向量叉乘只会得到一个向量,因为计算时只会使用两个法则之一,所以得到的结果应该是确定的。
那为何还需要w来确定方向呢?
这个w又是从何而来呢?

P49 除和除以的问题

第三段末“为了对矢量进行归一化,我们可以用矢量的模除以该矢量来得到。” 而下面公式则是矢量除以矢量的模。
应把“以”字去掉 。
可能是我太咬文嚼字了,除和除以是反的,查了一下,提出来,希望乐乐大大确定一下。
PS:我前几天买的书,却没有给我发第二版......

【常见问题】Unity阴影和深度纹理的补充说明(重要)

在9.4节,书里提到Unity会在一些平台上使用屏幕空间的阴影映射技术,并在后面解释了如何让物体投射和接收阴影。这里有一些内容需要补充。

  • 截止到Unity 5.4,当项目工程的目标平台是Mobile的时候,就不会使用屏幕空间的阴影映射技术,即使用原始的Shadows Map方法。在代码里,Unity会定义内置宏UNITY_NO_SCREENSPACE_SHADOWS来控制。而当项目工程的目标平台是支持屏幕空间阴影的话,例如PC, Mac & Linux Standalone平台(其他平台还没有验证过……有验证过的欢迎补充)时,会开启屏幕空间的阴影映射技术。正如书中P201下面所写的那样,由于宏UNITY_NO_SCREENSPACE_SHADOWS的定义与否,会生成不同的代码。读者可以通过帧调试器(Frame Debugger)来分辨当前是否使用了屏幕空间的阴影映射技术:

    2016-09-22 21 14 39 2016-09-22 21 32 51
    左边是PC平台,使用了屏幕空间的阴影映射技术,可以看到渲染事件明显较多,除了需要生成光源的阴影映射纹理,还增加了生成摄像机的深度纹理和生成屏幕空间阴影纹理的渲染事件。而右边是Mobile平台,使用的是传统的Shadow Map,只需要为光源生成阴影映射纹理。
    P.S.:左边在生成光源的阴影映射纹理时也显然比右边多了很多渲染事件,这是因为PC平台上为了效果和性能使用了Shadow Cascades技术,在上图里面4个cascades,而Mobile平台上Shadow Cascades是禁用的。

使用的是屏幕空间的阴影映射技术还是传统的Shadow Map也会影响阴影效果。

屏幕空间的阴影映射技术

此时LightMode为ShadowCaster的Pass会同时影响阴影投射和阴影接收的效果。具体来说,不管是希望物体能够向其他物体投射阴影还是自己接收来自其他物体的阴影,都需要定义LightMode为ShadowCaster的Pass。

因为对于投射阴影来说,需要使用LightMode为ShadowCaster的Pass来生成该光源的阴影映射纹理,这样如果它距离光源更近的话就会记录到阴影映射纹理中;对于接收阴影来说,也需要使用这个Pass来生成屏幕空间的深度纹理,从而可以在计算屏幕空间的阴影纹理时,可以据此来判断该点是否在阴影中。简单来说,因为阴影纹理是一张在正常渲染物体前就生成好的,因此**无论是阴影投射还是阴影接收,都需要定义LightMode为ShadowCaster的Pass**。

需要注意的是,是否会渲染到生成深度纹理不仅仅和LightMode为ShadowCaster的Pass相关,也和渲染队列有关。正如Unity文档中所说:

Note that only “opaque” objects (that which have their materials and shaders setup to use render queue <= 2500) are rendered into the depth texture.

因此,如果想要确保一个物体会被渲染到深度纹理中,我们需要保证它使用的shader:包含一个LightMode为ShadowCaster的Pass,并且它的渲染队列<= 2500。

综上,我们给出和屏幕空间的阴影相关的几个变量的使用规则:

渲染到Depth Texture 渲染到Shadowmap Receive Shadows的开关 Cast Shadows的开关
有一个LightMode为ShadowCaster的Pass,渲染队列<=2500 有一个LightMode为ShadowCaster的Pass,且打开了Cast Shadows 控制利用SHADOW_ATTENUATION等宏采样的结果,但不会影响Depth Texture和Shadowmap的渲染,换句话说它仅仅是不显示出来物体上的阴影而已 控制该物体是否会加入到Shadowmap的渲染

我们给出在屏幕空间的阴影映射技术下,要实现各个效果所需要的变量设置:

效果 Depth Texture Shadowmap Receive Shadows Cast Shadows 解释
接收阴影,且投射阴影 Y Y Y Y 标准情况,但如果是半透明物体的话会出现图1那样的情况,地板有穿帮。这是因为在渲染屏幕空间的阴影图时,由于在Depth Texture上地板区域的深度值被半透明物体覆盖,因此在计算阴影时计算的是半透明物体位置对应的阴影,而这部分区域是不在阴影内的
接收阴影,但不投射阴影 Y N Y N 为了不让物体渲染到Shadowmap就需要在物体面板关闭Cast Shadows,但同样对于半透明物体来说有明显穿帮,如图2所示,这是因为在渲染屏幕空间的阴影图时,通过Shadowmap判断地板区域没有物体挡住它,因此地板上有一块明显的非阴影区域
不接收/挡住阴影,但投射阴影 N Y - Y Receive Shadows只是控制宏采样的结果,不会影响是否渲染到Depth Texture,因此是否打开都不重要。这种情况下物体也不会显示任何来自其它物体的阴影,如图3所示

transparent_shadows

对于Unity 5的Standard Shader的实现,如果我们把它的Rendering Mode设置成Transparent,它的阴影处理方式是和上表中的第三种,即不接收/挡住阴影,但投射阴影,是相同的。除此之外,为了实现半透明阴影的效果,Standard Shader会配合使用Dither的方法来伪造效果。具体可参见issue

传统的Shadow Map

由于不会提前渲染Depth Texture,此时LightMode为ShadowCaster的Pass仅会影响阴影投射的效果。对于需要接收阴影的物体来说,由于它们会在普通的Pass中进行空间转换和判断是否在光源阴影内的计算,而不是提前生成的信息,因此它不需要定义LightMode为ShadowCaster的Pass也可以正确接收到其他物体的阴影。

为了完整性,我们给出给出和Shadow Map阴影相关的几个变量的使用规则:

渲染到Shadowmap Receive Shadows的开关 Cast Shadows的开关
有一个LightMode为ShadowCaster的Pass,且打开了Cast Shadows 控制利用SHADOW_ATTENUATION等宏采样的结果,但不会影响Shadowmap的渲染,换句话说它仅仅是不显示出来物体上的阴影而已 控制该物体是否会加入到Shadowmap的渲染

我们给出在Shadow Map阴影映射技术下,要实现各个效果所需要的变量设置:

效果 Shadowmap Receive Shadows Cast Shadows 解释
接收阴影,且投射阴影 Y Y Y 标准情况,如果是半透明物体的话会出现图1那样的情况。注意到这与屏幕空间阴影的效果是不同的,如图4
接收阴影,但不投射阴影 N Y N 为了不让物体渲染到Shadowmap就需要在物体面板关闭Cast Shadows。注意到这与屏幕空间阴影的效果也是不同的,如图5
不接收/挡住阴影,但投射阴影 Y N Y Receive Shadows控制宏采样的结果,因此我们只需要关闭Receive Shadows即可,如图6所示

transparent_shadowmap


这一点需要在书中9.4.2节第二部分”让物体接收阴影“中补充。

数中的表格

如果书中的表格可以像图片那样有单独的图片页面,甚至表格方便拷贝收藏就好了

第四章-错字勘误

第四章开头,第三段,“……为了让读者能够参与到计算中来,而不是‘填压式’(填鸭式)阅读……”

感谢分享

看作者博客多年,终于出书了,感谢作者分享,已下单,尝鲜后反馈!

【常见问题】关于法线转换的问题(以及在切线空间下计算法线纹理的问题)

有不少读者问我法线转换的问题,例如P128页的代码:

// Transform the normal from object space to world space
o.worldNormal = mul(v.normal, (float3x3)_World2Object);

很多人觉得这句话是个bug,但其实这段代码下面的文字已经详细解释过了:
169c7769-64be-477e-ae8f-adfa84370e0a

由于是入门书籍,因此当时在写这一段的时候自认为写的比较详细,加之在第4章单独开辟了一节(4.7节)作为解释,奈何有若干读者跳过了这一段,作为作者表示很伤心T_T……因此开了一个issue特此说明!

希望大家在看书的时候从前往后看,遇到有一些难以理解的不要急于跳过,尤其是第4章,这一章非常关键,法线这里会有问题的读者我相信肯定没有仔细读过第4章^_^。我在写书的时候会在重要的地方用文字强调很多遍,请大家能够仔细阅读下文字,不要急于看代码和敲代码,这样这本书才能给您带来最大的作用~

最后,感谢大家对本书的支持。

好书!

好书一本!!!
自己本身也是玩shader的 自己也买了一本
后来公司的美术也买一本来研究 ^^
(以上文字都是繁体转简体 不知道有没有问题!)

没有序

致谢里写着“感谢宣雨松和罗盛誉老师在百忙之中为本书写推荐序”,然而书里并没有序[微笑]。

第1版第2次印刷 P6勘误

章节 页码 位置 错误文字 正确文字
2.1.2 P6 第2段第1行 《Render-Time Rendering, Third Editon》 《Real-Time Rendering, Third Edition》

电子版

希望可以在亚马逊上搞个kindle电子版的,方便查看
平常上班,实在是不好意思把书拿出来,我已经买了书了,但是还是再有个电子版比较方便

第七章基础纹理,关于反射率albedo的问题

这一章里,常看到求albedo的公式:
fixed3 albedo = tex2D(_MainTex , i.uv).rgb * _Color.rgb ;
对反射率只有初中物理的印象,不知道这里为什么又采样又乘颜色,想问问这个公式是怎么来的。

关于勘误

你好,你的书讲解非常清晰易于理解,谢谢你的分享。
关于本书勘误,也能第一时间放出修改后的版本,很有责任心。
是否能有一个勘误列表,这样读者对手上的书也能更清楚的知道哪里需要修正?

谢谢。

P238 对广告牌锚点的疑惑

这页的中间,最开始定义锚点:
“选择模型空间的原点作为广告牌的锚点”
float3 center = float3(0,0,0);
这里的0对后面的计算不影响啊,不知道选原点(0,0,0)有什么意义?

从半透明到不透明的做法难点

作者你好。我现在才看到第八章透明混合。
我们项目有个需求是这样的,舰船进入场景的时候要求是半透明的,等待一定时间后由半透明渐变为不透明。那么半透明"Queue"="Transparent"。但在大部分时间里,舰船都是不透明的。
所以如果采用Transparent的做法,我猜测即便Alpha=1也是比默认的渲染队列要消耗更多资源,因为毕竟还存在Blend命令。
我有个偷懒的办法,就是准备两个材质球,一个是Transparent,一个是Geometry,让负责逻辑的程序自己去置换。
but,有没有一个好的办法可以在一个Shader里面解决呢?

漫反射代码中_World2Object的疑惑

作者你好,我在看书第6章128页关于漫反射代码中,其中转换法线坐标空间的代码是:
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)World2Object));
这是把法线从模型空间转换到世界空间中。
然而_World2Object不管怎么理解都像是从世界空间到模型空间。
所以我查了UnityCG.cginc,它也是从世界空间到模型空间应用_World2Object。
然后我把这里的代码替换为_Object2World,发现效果相同。
所以这个地方略微困惑。
此外我不太喜欢在世界坐标下计算光照,我觉得代码量略大,在模型空间中计算光照较为便捷^
^

关于半透明shader

我写的半透明shader会出现渲染顺序不对的情况,两个半透明的材质重叠的时候,有时后面的会显示在前面,这个问题困扰了我很久,不知道应该如何解决?
下面是我的代码。

关于混合纹理Shader及lightmap的问题

作者你好!我又来麻烦你了,这回又有2个问题。
你还记的Unity Shaders and Effects Cookbook这本书第2章第4节的混合纹理吗?我用片元着色器改写之后,用在项目中,前天出了个新问题。
这个场景在iPhone和三星的Pad上都运行正常,但在小米note3上运行,会报错:
09-05 11:31:18.441 25728-25754/com.seawar.i4 I/Adreno: ERROR: 0:116: 'phase0_gl_Position' : undeclared identifier
ERROR: 0:116: 'x' : field selection requires structure, vector, or matrix on left hand side
ERROR: 0:117: 'y' : field selection requires structure, vector, or matrix on left hand side
ERROR: 0:118: 'z' : field selection requires structure, vector, or matrix on left hand side
ERROR: 0:119: 'w' : field selection requires structure, vector, or matrix on left hand side
ERROR: 5 compilation errors. No code generated.
我查了下,发现Shader被编译后的phase0_gl_Position变量没被定义。为了弄清楚到底是哪一行代码引起的,我逐行注释打包,最后发现是v2f结构体中的这两句:
half4 uv0 : TEXCOORD0;
half4 uv1 : TEXCOORD1;
这个多出来的uv1导致编译的代码多了一部分,其中就包含了phase0_gl_Position。
我单独把这个场景打包为apk文件,发现在note3上是正常的!也就是说,同样的Shader,在项目中打包apk,运行会报错;单独打包,运行正常!
这个问题我一时无解,所以只好写个备胎,只使用一张材质了事。(有可能需要使用 target 3.0 来指定,我还没尝试)
这里我觉得有必要补充一个小知识点,我反复翻阅了,确定你在书中没有说^-^,在使用FallBack时,主纹理变量应尽量保持与备胎Shader相同的命名,因为如果当前Shader不被支持时,使用Unity自带的Shader作为备胎(相信多数人都是),如果主纹理变量命名不一样,有可能导致纹理丢失,备胎也就失去意义了。

第二个问题是,场景的lightmap丢失了。
我的程序员给我的反馈如下(也是网上查的):

unity5在某些设备上,对于这种动态加载的物体,无论你是否设置它的renderer的 lightmapIndex和 lightmapScaleOffset, 它都不会为其shader设置LIGHTMAP_ON这个关键字,如果看一下unity 支持light map的shader的源码,可以发现这个 LIGHTMAP_ON是控制显示烘培图的关键,但是这种情况unity5不会设置,并且你自己手动设置也不行。这里有一个方法,就是修改这个shader,自己加一组关键字,比如叫做DYNAMIC_LIGHTMAP_ON,就算没有定义LIGHTMAP_ON,定义了DYNAMIC_LIGHTMAP_ON也会触发lightmap的贴图,然后在代码里加载完毕动态场景后,设置这个keyword就行了

我仿佛记得你在书中的某个地方提到过烘培的事情,但想不起在哪,也找不到了,但是我还没尝试DYNAMIC_LIGHTMAP_ON是否真的起作用,想请教下作者这里的处理办法。
另:
今天的尝试是用自己写Shader烘培,是没得效果的,具体表现就是一团黑。
一定要烘培才能看得出来,设置为Baked GI,不用Realtime GI
为稳当期间也使用了书上第9章第2节你的源码,同样无法烘培。
如果使用Unity自带的Mobile/Diffuse则无问题。
查阅Mobile/Diffuse的源代码发现其中并无处理lightmap的内容,而是在Mobile/VertexLit中有两段Pass专门处理lightmap,这也好理解,想必是当前Shader不支持,则向上追溯。
但是在自己的Shader,或是在作者的Shader中添加FallBack "Mobile/VertexLit"并无任何作用。
昨天去github上说这个问题,作者没有回复,可能这段时间比较忙吧。
书我只看到第十章开始点点,不知道后面有没有说这个问题。

第一版223页有处文字错误

作者好,我又出现了……
第一版第223页,第一块代码后面的文字中有处错误。
原文是:“……得到切线空间下的3个坐标轴(xyz轴分别对应了副切线、切线和法线……)”
应该是:“xyz轴分别对应了切线、副切线和法线……”

4.3勘误

4.4.3习题中的5(2) 我算的是12倍根号3 而不是12倍根号2.
第54页 第二行cos60°=1/2=0.5 不应该用≈0.5

p67 倒数第五段第一句是否应该删除?

倒数第4段:”绕坐标系 E 下的 z 轴旋转 θz,绕坐标系 E 下的 y 轴旋转 ....“
倒数第5段:“绕坐标系 E 下的 z 轴旋转 θz,在坐标系 E 下绕 z 轴旋转 θz后的新坐标系 E'下的...“

我觉得倒数第5段的第一句好像有点奇怪,想问确认下
(这部分,网上的第四章pdf 版本和书里是一样的)

关于TEXCOORD数量的问题

作者你好,我又来麻烦你了。
在书的110页说到了TEXCOORD数量受到Shader Model的限制。我的一个Shader因为用到多通道混合纹理,所以数量上比较多,我的两个结构体是这样写的:

struct a2v{
    half4 vertex : POSITION;
    half2 uv0 : TEXCOORD0;
    half2 uv1 : TEXCOORD1;
    half2 uv2 : TEXCOORD2;
    half2 uv3 : TEXCOORD3;
    half2 magmaUV : TEXCOORD3;
    half3 normal : NORMAL;
};

还有一个是:

struct v2f
{
    half2 uv0 : TEXCOORD0;
    half2 uv1 : TEXCOORD1;
    half2 uv2 : TEXCOORD2;
    half2 uv3 : TEXCOORD3;
    half4 pos : SV_POSITION;
    half4 objectPos : TEXCOORD4;
    half3 normal : NORMAL;
    half3 sh : TEXCOORD5;
    SHADOW_COORDS(6)
    half2 magmaUV : TEXCOORD7;
};

上述代码都正常工作。
当然我没有在代码里面添加

pragma target 3.0

实际上加不加,对上述的代码都没有什么影响,所以我觉得上面并没有超出8个的限制。
问题出在a2v的这一句:
half2 magmaUV : TEXCOORD3;
如果我改成:
half2 magmaUV : TEXCOORD4;
马上会报错:
Shader error in 'WarShip/Shader_Scene_Volcano': Vertex program 'vert': unknown input semantics TEXCOORD/4
(on d3d11)
我猜测是TEXCOORD数量引起的,但是#pragma target 3.0这句代码依然是加不加都没影响。
混合纹理这个技术是学自《Unity着色器和屏幕特效开发秘笈》,它那个第29页的代码也是这个问题,照着写,直接报错:
Too many texture interpolaters......would be used for ForwardBase pass......(嗯……前向……)
当时我的解决方案只能是让Input结构体里面的uv变量不超过2个。
那本书除了数不清的翻译错误之外,查勘误极其麻烦,当然里面的介绍的技术还是比较实在,我知道你也读过……这次学习你的书后重新编写这部分代码,成功让TEXCOORD达到4个,但是第5个就……
这次我应该怎么办呢?

关于SH光源计算的代码

作者好,我看到9.2.2这节,基本上靠自己的理解写完了,根据“看上去正确,就是正确”这一原则,应该大致不差,但是书中并未给出计算SH的代码,我自己写的,想请你给瞧瞧看到底是不是这样:
在 v2f vert (a2v v)函数中我直接用了Shade4PointLights函数:
o.shade4Col = Shade4PointLights (unity_4LightPosX0,
unity_4LightPosY0, unity_4LightPosZ0, unity_LightColor[0].rgb,
unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,
unity_4LightAtten0, mul(_Object2World, v.vertex).xyz,
UnityObjectToWorldNormal(normalize(v.normal)));
return o;
接下来在 fixed4 frag (v2f i) : SV_Target 函数中,把shade4Col加上去:
half4 finalCol;
finalCol.rgb = ambient + (diffuse + spec) * atten + i.shade4Col;
finalCol.a = 1;
return finalCol;
根据测试,大致是这样子的,光源被设置为 not Important时,还是能够照亮胶囊,但是没得高光,感觉像是没得高光的lambert照明,但不及lambert变化细致,请看看是不是这样?

另外我没有按照书上的做法去区分光的类型并计算光的方向,因为我还是习惯在ObjectSpace下面去写代码,所以我在ForwardAdd的Pass中,计算光的方向时,没有区分光的类型,因为ObjSpaceLightDir函数已经做了这个事情,所以我直接用了:
half3 lightDir = normalize(ObjSpaceLightDir(i.pos));
half3 viewDir = normalize(ObjSpaceViewDir(i.pos));
请问我的理解对吗?

此外在衰减代码中有一句是dot(lightCoord, lightCoord).rr,对这个.rr是什么意思不是太明白呢?

p66 4.5.7 关于母牛妞妞缩放和平移的问题

我在unity中做了一个实验,对一个cube进行缩放 3 和平移(0,1,0),无论顺序如何,表现结果都是一样的。
transform.localToWorldMatrix:
3.00000 0.00000 0.00000 0.00000
0.00000 3.00000 0.00000 1.00000
0.00000 0.00000 3.00000 0.00000
0.00000 0.00000 0.00000 1.00000
平移矩阵并没有和缩放矩阵相乘,平移直接赋值了。
作者的讲法在数学上是没啥问题的。但是私以为,缩放应该是在模型坐标系做的,点缩放后的坐标变换也是相对模型坐标系而言的。这里容易引起误解,似乎用旋转和平移讲更合适一些。
我不是专业的图形程序员,如果有理解不对的地方,请谅解~

关于4.6 坐标空间有两个问题

1. 在第67页中间,讲到在Unity里的旋转顺序是zxy,得到的组合变换矩阵是MzMxMy = [...][...][...],没有看懂对于公式书写顺序的解释。
下面解释怎么从zyx顺序开始的?
我理解“旋转顺序颠倒一下,它们得到的结果就会是一样的”,但这和上面的公式的书写顺序有什么关系呢?我被绕晕了。

2. 在第69页里,讲确定在父坐标空间下的位置Ap,步骤2开始就没看懂,这里的“x轴”是说子空间还是父空间的?
步骤2里的Oc+axc,a为什么要乘xc呢?这是一个比例吗?

P128 第二个代码段中的fram,应该是from?

P128 第二个代码段中有句注释

// Transform the normal fram object space to world space

这里的fram应该是from吧?

另外就是,为什么这段代码要写成左乘转置矩阵的形式呢?直接右乘不是很顺手么?一眼看下去还以为_World2Object那个地方写错了呢···

P.250 的图 12.5 和 P.252 代码中的 Sobel 算子和 Prewitt 算子的Gx和Gy矩阵写反了.

P.250 的图 12.5 和 P.252 代码中的 Sobel 算子和 Prewitt 算子的Gx和Gy矩阵写反了.

Sobel 算子的 Gx 是用来计算 x 轴向(横向)的梯度, Gy 是用来计算 y 轴向(纵向)的梯度, 因此, Gx 是用像素点右侧相邻位置(右上,右中,右下)的像素点的亮度值(或高度值/灰度值), 分别乘以权重 1,2,1 后求和, 减去像素点左侧相邻位置(左上,左中,左下)的对应数值, 所以, Gx 矩阵应该是:

-1  0  1      
-2  0  2    
-1  0  1   

Gy 类似, 应为:

-1  -2  -1    
0    0   0    
1    2   1    

因为你的示例代码最终使用的是 1 - abs(edgeX)- abs(edgeY), 所以就算把梯度矩阵写反了也不影响, 如果你单独使用它们就会发现问题了, 比如用它们来生成法线贴图.

wiki上相关的条目:
Sobel算子
Prewitt算子

关于8.1开篇zwrite关闭的问题

经测试发现,透明物体在前,不透明物体在后的情况中,只有先绘制透明物体,再绘制不透明物体,才会出现透明物体完全挡住不透明物体的情况,与zwrite开关无关。
透明shader配置:Tags { "Queue" = "Transparent" } ZWrite On/Off Blend SrcAlpha OneMinusSrcAlpha
不透明shader配置:Tags { "Queue" = "Transparent+/-100" } ZWrite On
也就是说,几遍alpha blend时,即便打开了zwrite,只要保证不透明物体先绘制,是可以正常混合的。

练习题4.3.3的第7题有疑惑

在看到“如果该值为负,由左手法则判断可得到3个顶点的顺序是顺时针方向”这里就没有看懂。这里的左手法则是怎么摆弄的,怎么就是顺时针了?人眼方向这个条件是怎么用的?图因为没有旁注,我也没有看懂。希望能得到解答。

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

作者你好。
前次给你发了一个邮件说发布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

【常见问题】dot(lightCoord, lightCoord).rr和光源衰减的相关问题

P193的9.2节最下面有这样的代码:

#ifdef USING_DIRECTIONAL_LIGHT
    fixed atten = 1.0;
#else
    float3 lightCoord = mul(_LightMatrix0, float4(i.worldPos, 1)).xyz;
    fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#endif
  • _LightMatrix0:这个变量在书的P184页表格和官方文档中均有说明,它在内置的AutoLight.cginc文件中的特定宏下被定义,可以用于把点从世界空间变换到该光源的局部空间下。因此,上面代码第一句“mul(_LightMatrix0, float4(i.worldPos, 1)).xyz”就是把顶点坐标变换到光源空间下的坐标,这点在书中也有解释。

  • _LightTexture0:和_LightMatrix0类似的,也是在内置的AutoLight.cginc文件中的特定宏下被定义,紧跟_LightMatrix0的定义。它是一张包含了光源衰减信息的衰减纹理,我们可以用第一句代码得到的坐标进一步处理得到衰减纹理的采样坐标。由于Unity没有官方解释_LightMatrix0具体的数值范围含义,我们可以推测得到(推测方法可以是利用假彩色一节的方法来把这个变量当成颜色输出)_LightMatrix0得到的坐标模范围会是[0, 1],即与光源重合处是(0, 0, 0), 在光源范围的边界处模值为1。而 dot(lightCoord, lightCoord).rr一句,首先是由点积得到光源的距离平方,这是一个标量,我们对这个变量进行.rr操作相当于构建了一个二维矢量,这个二维矢量每个分量的值都是这个标量值,由此得到一个二维采样坐标。这个操作是shader中常见的swizzling操作,Cg的官网上也有说明。我们随后使用这个二维坐标对光照衰减纹理_LightTexture0进行采样,得到衰减值。关于_LightTexture0后面会系统讲。

  • 这段代码不够规范:书上有说这一节的代码不能直接用到项目里,仅仅是用来阐述原理,但如果你想用这段代码直接发布项目的话,是需要修改的,因为当时写的时候不够规范。原因是这段代码的keyword判断条件有问题,导致_LightTexture0或_LightMatrix0可能并没有被定义。我们可以将原来的代码替换成下面的代码(新的代码也已经push到了github上):

    #ifdef USING_DIRECTIONAL_LIGHT
        fixed atten = 1.0;
    #else
        #if defined (POINT)
            // 把点坐标转换到点光源的坐标空间中,_LightMatrix0由引擎代码计算后传递到shader中,这里包含了对点光源范围的计算,具体可参考Unity引擎源码。经过_LightMatrix0变换后,在点光源中心处lightCoord为(0, 0, 0),在点光源的范围边缘处lightCoord模为1
            float3 lightCoord = mul(_LightMatrix0, float4(i.worldPos, 1)).xyz;
            // 使用点到光源中心距离的平方dot(lightCoord, lightCoord)构成二维采样坐标,对衰减纹理_LightTexture0采样。_LightTexture0纹理具体长什么样可以看后面的内容
            // UNITY_ATTEN_CHANNEL是衰减值所在的纹理通道,可以在内置的HLSLSupport.cginc文件中查看。一般PC和主机平台的话UNITY_ATTEN_CHANNEL是r通道,移动平台的话是a通道
            fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
        #elif defined (SPOT)
            // 把点坐标转换到聚光灯的坐标空间中,_LightMatrix0由引擎代码计算后传递到shader中,这里面包含了对聚光灯的范围、角度的计算,具体可参考Unity引擎源码。经过_LightMatrix0变换后,在聚光灯光源中心处或聚光灯范围外的lightCoord为(0, 0, 0),在点光源的范围边缘处lightCoord模为1
            float4 lightCoord = mul(_LightMatrix0, float4(i.worldPos, 1));
            // 与点光源不同,由于聚光灯有更多的角度等要求,因此为了得到衰减值,除了需要对衰减纹理采样外,还需要对聚光灯的范围、张角和方向进行判断
            // 此时衰减纹理存储到了_LightTextureB0中,这张纹理和点光源中的_LightTexture0是等价的
            // 聚光灯的_LightTexture0存储的不再是基于距离的衰减纹理,而是一张基于张角范围的衰减纹理
            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和_LightMatrix0只在某些条件下会被定义,例如在开启了POINT、SPOT、POINT_COOKIE、DIRECTIONAL_COOKIE等,具体我们可以在AutoLight.cginc里面找到。但因为原来的代码里没有进行严格判断,在发布的时候由于Unity会严格编译Unity Shader根据不同的keyword生成对应的shader program,此时它就会发现我们的错误了。

更多关于keyword和shader variants的内容可以参见issue中作者的回复。

基于距离的衰减纹理

上面提到,在点光源的情况下,基于到点光源中心距离的衰减纹理被存储在了_LightTexture0中,而在聚光灯的情况下,这张纹理被存储在了_LightTextureB0中。对它们的采样都是使用光源空间到点到光源中心的距离平方来作为采样坐标。这张纹理可以摆放一张平面来输出查看(要注意输出通道UNITY_ATTEN_CHANNEL):
2016-09-29 00 05 25
可以看出,它相当于一张一维纹理,在坐标0处值为1,在坐标1处值为0,这意味着,当距离光源越近,atten值越接近于1。

基于张角的衰减纹理

前面说过,对于聚光灯来说,衰减不仅仅受距离影响,也受张角影响。再回顾下,聚光灯条件下,总的衰减代码如下:

fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;

最后的tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL这句不再说明,和上面一样。最前面的(lightCoord.z > 0)是判断方向范围,因为聚光灯的张角范围小于180°,因此如果lightCoord.z <= 0的话它肯定不会被照亮,衰减值就直接是0。

比较难理解的是tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w这句话。总体来讲,这句代码是在基于点的张角来计算衰减,如果和光源空间的中心轴(+z轴)刚好重合,那么这个衰减值应该是1,而在聚光灯的张角边缘处,这个值因为是0。由网友提供的Unity 4.3源码,我们可以知道聚光灯_LightMatrix0的计算(P.S. 由于下面源码不全,所以并不是完整的_LightMatrix0,从实验结果来看应该还做了一个粗略的范围判断,等下好了源码再补吧):

Light::GetMatrix (const Matrix4x4f* __restrict object2light, Matrix4x4f* __restrict outMatrix) const
{
    Matrix4x4f temp1, temp2, temp3;
    float scale;

    switch (m_AttenuationMode) {
    case kSpotCookie:
        // we want out.w = 2.0 * in.z / m_CotanHalfSpotAngle
        // c = m_CotanHalfSpotAngle
        // 1 0 0 0
        // 0 1 0 0
        // 0 0 1 0
        // 0 0 2/c 0
        // the "2" will be used to scale .xy for the cookie as in .xy/2 + 0.5 
        temp3.SetIdentity();
        temp3.Get(3,2) = 2.0f / m_CotanHalfSpotAngle;
        temp3.Get(3,3) = 0;

        scale = 1.0f / m_Range;
        temp1.SetScale (Vector3f(scale,scale,scale));

        // temp3 * temp1 * object2Light
        MultiplyMatrices4x4 (&temp3, &temp1, &temp2);
        MultiplyMatrices4x4 (&temp2, object2light, outMatrix);
        break;

看似难懂,但其实上面就是先把点的坐标变换到聚光灯坐标空间下(object2light矩阵),然后在乘以temp3 * temp1。我们把上面的公式拆开,就会发现一个点和上面的矩阵相乘后结果是:

_LightMatrix0 * (x, y, z, 1) = temp3 * temp1 * object2Light * (x, y, z, 1) = (x/scale, y/scale, z/scale, 2z/(scale * m_CotanHalfSpotAngle)

而在shader中,我们靠lightCoord.xy / lightCoord.w + 0.5得到的结果其实是:

uv = lightCoord.xy / lightCoord.w + 0.5 = (x * m_CotanHalfSpotAngle/(2 * z) + 0.5, y * m_CotanHalfSpotAngle/(2 * z) + 0.5)

我们以x分量为例,x * m_CotanHalfSpotAngle/(2 * z) + 0.5其实是把点的tan值除以半张角的tan值(即等于乘以cot值m_CotanHalfSpotAngle),由此得到张角比值,再通过缩放和平移,把张角的判断范围归一到[0, 1],如下图所示:
untitled

也就是说,在张角中心坐标值为0.5,在两侧分别为0和1。而这张基于张角的衰减纹理_LightTexture0是长这样的(它的w分量):
2016-09-29 00 31 33
由此就不难看出,在张角中心,即坐标0.5处衰减值为1,而在两侧是接近0的。

关于13.2节运动模糊特效的问题

作者你好
书中13.2节使用深度纹理实现了运动模糊效果,其中在计算当前像素位置在世界空间下的坐标时使用的代码是这样的:
float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth); float4 H = float4(i.uv.x * 2 - 1, i.uv.y * 2 - 1, d * 2 - 1, 1); float4 D = mul(_CurrentViewProjectionInverseMatrix, H); float4 worldPos = D / D.w;
其中H是当前像素在NDC下的坐标,也就是世界坐标经过VP矩阵转换后得到的齐次坐标再除以w分量的结果。现在我们用这个H直接使用VP矩阵的逆矩阵进行变换,得到的结果应该是缺少w系数的坐标,按说最后应该是D * D.w,但不知道这里为什么要除以D.w。
我看了《GPU Gems 3》里面的文章,作者对推导过程也没有做详细的解释,所以我想问一下这里做除法的数学推导是怎么做出来的。
多谢了。

第70页的勘误

Ap公式的第一行的第一个矩阵的左下角,多打了一个C。

关于书本代码进行手机测试的问题

先跪作者写着本书,好东西啊。
上个礼拜把书看完了,里面的相关例子在电脑上正常通过。不过我在移动设备,比如6S上则是有不少的例子都挂了直接黑掉了,看了下作者的CSDN的关于高效shader的博客做了下优化不过没用。同时对于target的设定也做过更改,也没什么用
所以大胆请教下,这是和shader的内部的计算复杂有关还是和unity的设置有关。

还有个问题,想问下,在unity shader里除了顶点着色器和片元着色器的交互变量会使用寄存器,那么在Properties里面设置的变量也会使用寄存器吗?(可能书里面说了 我没看仔细 问错了请见谅)

这两个问题自己有点搞不明白,所以请教下作者,先谢谢了。

5.2中代码的错误

104,105,106三页的代码片段中,输入和书上一样的shader代码会报错,提示invalid output semantic 'SV_POSITION': Legal indices are in [0,0] at line 22 (on d3d11)
在删除了v2f vert(a2v v)后的 SV_POSITION 后显示结果正常,应该是在顶点着色器输出v2f结构后,就不能使用SV_POSITION语义来标识顶点着色器的输出

关于12章运动模糊问题

作者 您好
想请问下关于书中第12章屏幕后处理效果之运动模糊中我的两个个疑问

我理解您的思路应该是当前帧和前一帧数据做一个accumulation。用到了两个Pass。

第一个问题:
您用到accumulationTexture.MarkRestoreExpected()这样一个函数,网上搜的内容也比较少,看你的解释是不让unity把上一帧(accumulationTexture)的信息删除掉。但是 Graphics.Blit(src, accumulationTexture, material)这句就是记录了上一帧accumulationTexture的值 代码里也没有清空accumulationTexture值得操作。所以本来值应该就是一直保存的啊

主要就是第二个问题:
第一个pass是做Alpha Blend。 ColorMask RGB 只写入rgb数据到颜色缓冲区。那这时候的颜色缓冲区的A值还是上一帧的A值(也就是目标缓冲区里的A值 ,是不是1?)让我理解应该就到此为止就行了。但是您又进行了第二个Pass。书中说是为了确保写入的是1。但是我自以为好像没有必要,做了很多实验。甚至我极端的把第二个Pass 写入的a值设置为0。运行起来感觉也没有什么变化。

能否请解答下 我理解的第一个pass思路是不是正确的,还有就是第二个pass存在的必要性(一定是有作用的,强大的作者肯定有自己的道理)。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.