-
Shader开发从入门到精通
2015-09-17 09:32:32Shader编程从入门到精通视频教程,该课程主要分享2D中的Shader与3D中的Shader编程,具体功能包括颜色配置、纹理、UV动画、滤镜等。 -
unity-shader-ShaderGraph可视化shader
2019-05-05 11:33:28title: unity-shader-ShaderGraph可视化shader categories: Unity3d tags: [unity, shader, ShaderGraph, 可视化] date: 2019-05-03 18:04:23 comments: false unity-shader-ShaderGraph可视化shader, 和 shader ...
title: unity-shader-ShaderGraph可视化shader
categories: Unity3d
tags: [unity, shader, ShaderGraph, 可视化]
date: 2019-05-03 18:04:23
comments: false可视化shader, 和 shader forge 插件, ue4 材质编辑器 类似的东西, 都是通过可视化链接节点来编写逻辑代码, 然后会自动 编译 就能看到效果.
ShaderGraph 需要用到 lightweight rp. 里面内置了一些常用的公式 如: frensel, 很简单就可以使用. 可见写 shader 的门槛越来越低了, 如果知道基点的实现原理会更好, 这个可以通过官网文档查阅到, 每个节点都有对应的代码实现. 官网文档 - https://docs.unity3d.com/Packages/com.unity.shadergraph@6.5/manual/index.html
前篇
- 官方资料
- 【Shader Graph教程】轻松学习Unity2018内置shader可视化编辑器Shader Graph (B站不错的教程, 没有必要全看, 有点冗余) - https://www.bilibili.com/video/av32162824/
- 不错的示例仓库: https://github.com/keijiro/ShaderGraphExamples
要点击左上角的 save asset 才会编译生效, 这个和 ue4 的 apply 一样
疑问
-
Q: 支持多pass?
官方解释貌似不支持.
We don't expose concepts like 'multi-pass' to the graph
参考: https://forum.unity.com/threads/feedback-wanted-shader-graph.511960/page-5 -
Q: 深度测试, 深入写入 等等 frag op?
貌似不支持
参考: https://www.reddit.com/r/Unity3D/comments/9sho4x/is_there_any_way_to_change_ztest_in_the_shader/
上面几个问题还是得用以前的写 xxx.shader 代码的方式去解决.
相关使用方式
shader 命名规则
- reference 属性的 _MainClr 才是 uniform 变量的命名, 也就是程序设置参数的命名.
替换预览模型
预览节点右键 custom mesh 就可以选择别的模型
安装 shader graph
当前版本 unity2018.3.11f1. 安装了 shader graph 后, 之前写的 xxx.shader 将会无效 (变成紫色).
所以最好还是另起一个lwrp的模板工程用来学习.参考: Unity-Shader Graph 安装 - https://blog.csdn.net/liquanyi007/article/details/80771441
-
默认的 3d 工程没有 shader graph, 需要通过 package manager 安装 lightweight rp, shadergraph 这两个包, 才能使用 shader graph.
如果是 2019 之前的是预览版, 需要在 advance 勾选上 show preview packages 才能显示出来
-
创建一个 lwrp asset. create -> rendering -> lightweight pipeline asset
然后指定项目使用这个 asset, 才肯使用 shader graph. Edit -> Project Setting -> Graphics
-
然后 create -> shader 才会出现有 graph 的选项
自定义节点 custom node
相关参考:
- Shader Graph Custom Node API: Using the Code Function Node - https://blogs.unity3d.com/2018/03/27/shader-graph-custom-node-api-using-the-code-function-node/
- Custom Nodes - https://docs.unity3d.com/Packages/com.unity.shadergraph@6.5/manual/Custom-Nodes-With-CodeFunctionNode.html
-
增加一个自定义节点脚本
using System.Reflection; using UnityEditor.ShaderGraph; using UnityEngine; [Title("Custom", "My Custom Node1")] // 创建节点时 节点所在的组, 可有n多个层级 public class MyCustomNode : CodeFunctionNode { public MyCustomNode() { name = "My Custom Node2"; // graph 中节点名字 } protected override MethodInfo GetFunctionToConvert() { return GetType().GetMethod("MyCustomFunction", BindingFlags.Static | BindingFlags.NonPublic); } // 计算公式 static string MyCustomFunction( [Slot(0, Binding.None)] DynamicDimensionVector A, [Slot(1, Binding.None)] DynamicDimensionVector B, [Slot(2, Binding.None)] out DynamicDimensionVector Out) { return @" { Out = A + B; } "; } }
-
然后就可以在 graph 中使用
- 节点可以是 vector1,2,3,4, 只要 A 或 B 其中一个换成其他参数类型, 其他两个节点也会跟着变化
数据类型
Name Color Description Vector 1 Light Blue A Vector 1 or scalar value Vector 2 Green A Vector 2 value Vector 3 Yellow A Vector 3 value Vector 4 Pink A Vector 4 value Dynamic Vector Light Blue See Dynamic Data Types below Matrix 2 Blue A Matrix 2x2 value Matrix 3 Blue A Matrix 3x3 value Matrix 4 Blue A Matrix 4x4 value Dynamic Matrix Blue See Dynamic Data Types below Dynamic Blue See Dynamic Data Types below Boolean Purple A Boolean value. Defined as a float in the generated shader Texture 2D Red A Texture 2D asset Texture 2D Array Red A Texture 2D Array asset Texture 3D Red A Texture 3D asset Cubemap Red A Cubemap asset Gradient Grey A Gradient value. Defined as a struct in the generated shader SamplerState Grey A state used for sampling a texture
常用节点
法线节点
sample texture 2d 的 Type 的设置为 Normal 才是 法线图 的采样, 得到蓝色的才是正确的
Default 为普通贴图的采样
边缘 rim
fresnel effect, 使用很方便, 已经把 世界空间 下的 法线和观察方向 算好用来 dot 处理, 只需要填充一下 指数 Power 即可.
ps: 这些节点如果了解过 fresnel 公式的话自然就比较清楚这些节点都干了什么事情.
纹理uv偏移
tilling and offset, 相当于
o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);
中的 TRANSFORM_TEX 宏做 uv流动 效果的时候就需要用到这个节点
uv流动的几种方式
-
基于 纹理uv. 模型顶点的uv值. (看起来有点乱, 因为 uv值 的是在 uv展开 的时候决定的)
-
基于 顶点位置, 又分为 切线,对象,世界,观察 四个空间的
-
观察空间下
-
-
基于 屏幕空间的位置. 简答的理解就是屏幕的 左下角是(0,0), 右下角是(1, 1), 顶点都位于这个区间内
appdata 节点
也就是 cpu 到 gpu 的数据结构中的字段, 比如
struct appdata_full { float4 vertex : POSITION; float4 tangent : TANGENT; float3 normal : NORMAL; float4 texcoord : TEXCOORD0; float4 texcoord1 : TEXCOORD1; float4 texcoord2 : TEXCOORD2; float4 texcoord3 : TEXCOORD3; fixed4 color : COLOR; };
在 shader graph 中为以下节点
Name Data Type Description Position Vector 3 Vertex or fragment position, label describes expected transform space Tangent Vector 3 Vertex or fragment tangent vector, label describes expected transform space Normal Vector 3 Vertex or fragment normal vector, label describes expected transform space Bitangent Vector 3 Vertex or fragment bitangent, label describes expected transform space UV Vector 2 UV0, UV1, UV2, UV3 Vertex Color Vector 4 RGBA vertex color value. - 参考文档: Port Bindings - https://docs.unity3d.com/Packages/com.unity.shadergraph@6.5/manual/Port-Bindings.html
示例说明
积雪
step 函数使用的频率非常高, 常用于条件的判断 (替代 if-else, 更优于gpu并行计算). 之前也总结过 unity-shader-GPU优化_step函数替代if-else.
step(a, b) : 如果 a <= b,返回 1 ;否则,返回 0
官方示例中的 积雪 效果
- snow direction : (0, 1, 0), 也就是y轴正方向
- snow depth : 0, 这一步step也就是判断哪些 面 的 法线 与 (0, 1, 0) 的夹角小于 90 度
卡通
- 光的方向与法线的 点乘 结果从 [-1, 1] 映射到 [0, 1] (映射算法
(x+1)/2
), 再去采样 Tamp 贴图作为光照的强度.
常用快捷键
various shortcut keys to use for better workflow.
Hotkey Windows OSX Description Focus F F Focus the workspace on all or selected Nodes Create Node Spacebar Spacebar Opens the Create Node Menu -
Unity Shader 基础教程
2018-05-27 19:14:23在Github上看到一篇关于Unity-Shader的教程,感觉还不错,作者写的很好,很适合Unity-Shader的基础入门,我在这里翻译一下,分享给大家,英文水平很烂,大致能明白Unity-Shader是什么,渲染管线的工作流程,以及...Unity-Shader-基础教程
在Github上看到一篇关于Unity-Shader的教程,感觉还不错,作者写的很好,很适合Unity-Shader的基础入门,我在这里翻译一下,分享给大家,英文水平很烂,大致能明白Unity-Shader是什么,渲染管线的工作流程,以及Unity Shader的一些类型和怎样编写Unity Shader。
(原文链接)第一部分:什么是Shader?
Shader是计算机图形渲染管线的一部分,它是一小段应用程序,告诉计算机在场景中怎样对物体渲染和着色。这个过程包括计算颜色和光照值,并将其给予对象,以至于对象被显示在屏幕上。和上面一样,Shader也被用来创建游戏中的一些特殊的和后期处理效果。
在现代游戏引擎中(包括Unity),Shader运行在可编程的GPU渲染管道中,在GPU中允许并行运行,并且能够很快速的进行许多着色计算。
第二部分:渲染管道
为了学习Shader,我们将简单的了解渲染管道,我们将在本教程中讨论下面这张图片:
我更加倾向把Shader看做是由一种信息类型(模型数据、颜色等等)到另外一种信息类型(像素/片元)的变换,对象数据继承与对象本身,例如模型中的点、法线、三角形、UV坐标等等。我们可以把自定义数据/传递到shader中使用,颜色、纹理、数字等等这些。
着色器流水线的第一步是顶点函数。正如你所知的,顶点就是一些点。顶点函数将处理模型中的一些点(连同其它的一些数据诸如法线)并将它们渲染流水线的下一个阶段,片元函数。
片元函数将使用这些顶点,并对它们着色。将它想象为一个画家和他们的画笔,它最终以(R,G,B,A)的格式输出像素数据。
最后,将像素添加到帧缓冲中,在帧缓冲中这些数据有可能被进一步处理,直到这些数据被绘制到屏幕上。
第三部分:Scene 配置
在开始写Shader代码之前,需要在Unity中配置一下我们的场景。在Unity中创建一个工程,并导入所有的资源。
- Bowl Model(碗模型)
- Noise Texture(噪声纹理)
- Bowl texture(碗模型纹理)
在新场景中添加一个Cube、Sphere和Bowl Model(碗模型),并保存场景,之后,你的场景将向下面这样:
接下来,在Project视图中单击右键(或者点击Create)并添加一个新的Unlit Shader(无光照着色器),将其命名为Tutorial_Shader.
如果你对其它类型的shaders好奇的话,我会在文章的末尾谈论它。
然后在刚才创建的shader上点击右键Create>Material 创建一个材质球,Unity将自动创建一个材质球并使用刚才创建的着色器的名字。
Note:一个“Material”在Unity中仅仅是着色器的一个实例,它仅保存自定义数据/属性的值。
最后,通过单击或者拖动的方式将材质赋予我们在场景中创建的所有对象上。
之后场景中的每个对象看起来是这样的,白色,并且没有阴影或者shading:
第四部分:一个Unlit Shader(无光照着色器)的大致骨架
终于到了开始写我们自己的shader的时候了,在写之前,首先打开之前创建的Tutorial_Shader文件,你将看到Unity自动生成了一些代码供我们使用。为了继续本教程,删除所有代码并使文件变空白。
Note:所有的shader在Unity中使用的是一种被称为shaderlab的语言写的。Shadrlab是HLSL/CG语法的包装器,为了使Unity能够跨平台编译Shader代码并且在Inspector面板中暴露一些属性结点。
我们将添加一些初始代码,如下:
Shader "Unlit/Tutorial_Shader"{ ... }
这行代码的作用是指定着色器代码存放在什么位置。双引号中的字符串告诉Unity在哪里查找Shader.
例如:
Shader "A/B/C/D/E_Shader"{ ... }
如果你保存你的shader代码,并切回到Unity中,你将注意到所有使用这个材质的对象都已经变成了粉红色。
当你的着色器中有错误时,Unity中将调用FallBack着色器,你将会得到一个粉红色的物体。你可以在Project中点击shader文件查看相应的错误。目前,我们得到一个粉红色的物体,因为我们的shader文件还没有完成。
接下来是属性块,如下:
Shader "Unlit/Tutorial_Shader"{ Properties{ ... } }
在属性块中,我们可以传递一些自定义数据。我们在这里声明的数据将被显示在Unity Editor面板中,在Editor中更改也会驱动脚本更改。
Shader "Unlit/Tutorial_Shader"{ Properties{ ... } SubShader{ ... } }
每一个shader有一个或者多个subshaders,如果你的应用将被部署到多种平台(移动、PC、主机),添加多个Subshader是非常有用的。例如:你可能想要写为PC/Desktop写一个高质量的Subshader,为移动端写一个低质量,但速度更快的Subshader.
pass语句块:
Shader "Unlit/Tutorial_Shader"{ Properties{ ... } Subshader{ Pass{ ... } } }
每个Subshader至少有一个pass语句块,它实际上是对象渲染的位置。一些特效要求有多个pass语句块,目前,我们仅仅专注于一个。
在pass语句块中,是一些实际渲染的代码:
Shader "Unlit/Tutorial_Shader"{ Properties{ } Sunshader{ pass{ CGPROGRAM ... ENDCG } } }
我们实际写的所有Shader代码都在CGPROGRAM和ENDCG中,对于Unity来说,shaderlab是HLSL和CG的变体。
下面,我们需要告诉Unity,顶点函数和片元函数是什么:
CGPROGRAM #pragma vertex vertexFunction #pragma fragment fragmentFunction ENDCG
这里,我们将vertex函数声明为vertexFunction,fragment函数声明为fragmentFunction.
我们也将定义这些函数:
CGPROGRAM #pragma vertex vertexFunction #pragma fragment fragmentFunction void vertexFunction(){ } void fragmentFunction(){ } ENDCG
在开始着色之前,我们需要设置一些数据结构和两个函数,这样,我们就可以使用Unity给定的数据,并把数据返回到Unity中。首先,添加UnityCG.cginc语句块,我们可以使用这个文件中包含的一些助手函数。
我们将添加一个数据结构a2v(此处和原文不一致),并修改顶点函数,将a2v作为参数传递给顶点函数。
CGPROGRAM #pragma vertex vertexFunction #pragma fragment fragmentFunction #include "UnityCG.cginc" struct a2v{ }; void vertexFunction(a2v v){ } void fragmentFunction(){ } ENDCG
当传递一个参数到vertexFunction中时,Unity将解析这个函数的结构,并基于正在绘制的对象模型传递值。我们可以传递一些自定义的数据,如下面声明的那样:
[type] [name] :[semantic]
例如,可以要求Unity获取模型对象的顶点坐标,如下:
flot4 vertex:POSITION;
我们也可以从Unity中获取顶点坐标和UV纹理坐标,如下:
struct a2v{ float4 vertex:POSITION; float2 uv:TEXCOORD0; }
最后配置顶点函数,创建一个结构体,并将其命名v2f(代表从vertex to fragment,顶点数据传递到片元),将vertex中包含的数据传递到片元函数,同时确保vertexFunction 返回 v2f的数据类型,在我们使用它时,创建并返回一个空白数据。
CGPROGRAM #pragma vertex vertexFunction #pragma fragment fragmentFunction #include "UnityCG.cginc" struct a2v{ float4 vertex:POSITION; float2 uv:TEXCOORD0; }; struct v2f{ }; v2f vertexFunction(a2v v){ v2f o; return o; } void fragmentFunction(){ } ENDCG
像之前一样,我们可以在v2f结构体中定义一些数据,我们可能想要把这些数据从顶点函数传递到片元函数。
struct v2f{ float4 position:SV_POSITION; float2 uv:TEXCOORD0; }
如果你对SV_POSITION和POSITION 感到好奇,SV代表“system value”,在v2f结构中表示最终渲染的顶点的位置。
现在基本准备好了,我们仅仅需要编辑片元函数,使它接受一个v2f结构并返回一个fixed4的值。
fixed4 fragmentFunction(v2f i){ }
输出的片元函数将是一个有(R,G,B,A)代表的颜色值
最后,我们将为片元函数添加一个SV_TARGET的输出语义,如下:
fixed4 fragmentFunction(v2f i):SV_TARGET{ }
这个过程告诉Unity我们将输出一个color去渲染,现在准备开始实际的编码了,肉和土豆使我们的vertex和fragment函数,到这个点,大致的骨架已经出来了
Shader "Unlit/Tutorial_Shader"{ Properties{ } Subshader{ Pass{ CGPROGRAM #pragma vertex vertexFunction #pragma fragment fragmentFunction #include "UnityCG.cginc" struct a2v{ float4 vertex:POSITION; float2 uv:TEXCOORD0; }; struct v2f{ float4 position:SV_POSITION; float2 uv:TEXCOORD0; }; v2f vertexFunction(a2v v){ v2f o; return o; } fixed4 fragmentFunction(v2f i):SV_TARGET{ } ENDCG } } }
第五部分:Shader 基础
首选我们要做的是获取顶点的正确位置,使用Unity中提供的UnityObjectToClipPos()函数(这个函数的作用是将世界空间的模型坐标转换到裁剪空间,函数内部封装了实现顶点坐标变换的具体细节,如矩阵变换等等),如下:
v2f vertexFunction(a2v v){ v2f o; o.position=UNnityObjectToClipPos(v.vertex); return o; }
这个函数将在局部空间中表示的顶点,变换到渲染相机的裁剪空间。注意,我们通过设置o.position的位置来传递转换的点。接下来,给片元函数一个输出。
fixed4 fragmentFunction(v2f i):SV_TARGET{ return fixed4(0,1,0,1); }
现在,等待一会儿。保存你的shader并且返回到Unity,你将看到我们的精美的绿色的物体。如下:
当然,这对你来说可能印象并不深刻,因此,让我们继续构建,而不是返回一个基本的绿色,可能我们想要编辑shader使得其能返回一个我们想要的颜色,为了做到这一点,我们需要回到开始的自定义属性。
我们可以使用如下语法添加一些属性:
name ("display name",type)=default value
如下,我们将暴露出一个颜色值,如下:
Properties{ _Color("Totally Rad Color",Color)=(1,1,1,1) }
在这里定义了一个颜色供我们使用,将其称之为_Color并且它将显示为 “Totally Rad Color!”,在Unity面板中。我们也将给予一个默认白色的值,现在保存并返回Unity,在Inspect的材质面板中,你将看到如下:
在我们使用这个color之前,我们需要把它传递到CG代码中,Unity会通过变量的名字进行自动绑定,如下:
CGPROGRAM #pragma vertex vertexFunction #pragma fragment fragmentFunction #include "UnityCG.cginc" struct a2v{ float4 vertex:POSITION; float2 uv:TEXCOORD0; }; struct v2f{ float4 position:SV_POSITION; float2 uv:TEXCOORD0; }; //从CG中获取属性 float4 _Color; v2f vertexFunction(a2v v){ v2f o; o.position=UnityObjectToClipPos(v.vertex); return o; } fixed4 fragmentFunction(v2f i):SV_TARGET{ return fixed4(0,1,0,1); } ENDCG
现在,可以在片元函数中使用_Color值了,让它返回我们期待的颜色值,而不是返回一个绿色:
fixed4 fragmentFunction(v2f i):SV_TARGET{ return _Color; }
现在,保存并返回到Unity中,如果你在Inspect中的Material中改变_Color的值,你应该能看到所有对象做出了相应的改变。
现在我们知道了如何添加属性,让我们尝试添加一张标准的纹理贴图,这里需要添加一个新的属性给我们的纹理:
Properties{ _Color("_Color",Color)=(1,1,1,1) _MainTexture("Mian Texture",2D)="white"{} }
注意它的类型是2D,默认给它一张白色的纹理,我们还需要获取这个属性在CG片段中使用它:
float4 _Color; sampler2D _MainTexTure;
然后,需要从模型传递UV纹理坐标到片元函数,我们可以通过返回顶点函数并将其传递v2f中,如下:
v2f vertexFunction(a2v v){ v2f o; o.position=UnityObjectToClipPos(v.vertex); o.ov=v.uv; return o; }
为了能在片元函数中使用纹理的颜色,我们需要对纹理进行采样。谢天谢地,CG中已经有一个tex2D()函数帮我们做了一切。
fixed4 fragmentFunction(v2f i):SV_TARGET{ return tex2D(_MainTexture,i.uv); }
tex2D获取我们想要采样的纹理以及我们想要采样的UV坐标,在这种情况下。我们提供了它的主纹理并给定模型的点,我们可以得到我们想要的颜色,最后返回的是最终的颜色。现在,保存并返回到Unity 的material insepct面板中,选择bowel纹理赋予"Main Texture"",你会发现,模型发生了改变,尤其是碗的模型看起来尤其像一碗汤。
提示:我们可以改变纹理在Unity中的采样方式,通过选择纹理文件并在Inspector面板中改变filter mode(过滤模式):
第六部分:试着改变Shader
现在,我们已经大致了解了一些基础,我们可以做一些有趣的效果并做一些简单的特效。首先,我们将使用一张噪声贴图实现“溶解” 或者“切断”效应,首先我们将添加另一个纹理属性和一个float 属性,如下:
Properties{ _Color("Color",Color)=(1,1,1,1) _MainTexture("Main Texture",2D)="white"{} _DissolveTexture("Dissolve Texture",2D)="white"{} _DissolveCutoff("Dissolve Cutoff",Range(0,1)=1 }
注意这里是如何设置_DissolveCutoff 为一个Range(0,1),它代表一个从(0,1)(包含)的float值,并且这种计数法允许我么容易的使用slider(滑动条)来设置值,接下来,让我们在CGPROGRAM中添加他们。
float4 _Color; sampler2D _MainTexture; sampler2D _DissolveTexture; float _DissolveCutoff;
现在能在片元函数中对溶解纹理采样:
fixed4 fragmentFunction(v2f i):SV_TARGET{ float4 textureColor=tex2D(_MainTexture,i.uv); float4 dissolveColor=tex2D(_DissolveCutoff,i.uv); return textureColor; }
提示:我们将为我们的主纹理使用相同的UV纹理坐标,接下里,魔术发生了:
fixed4 fragmentFunction(v2f i):SV_TARGET{ float4 textureColor=tex2D(_MainTexture,i.iv); float4 dissolveColor=tex2D(_DissolveTexture,i.uv); clip(dissolveColor.rgb-_DissolveCutoff); return textureColor; }
clip 函数检查这个给定的值是否小于0.如果小于0,我们将丢弃这个像素并且不绘制它。如果大于0,继续保持像素、正常的渲染,我们的代码的工作方式如下:
- 对主纹理的颜色进行采样
- 对纹理颜色进行裁剪采样
- 从裁剪纹理中减去裁剪值
- 如果小于0,不进行绘制
- 否则,返回主纹理采样颜色
现在,保存并返回Unity,回到材质面板,赋予“Dissolve Texture"我们的noise纹理,移动”Dissolve Cutoff" 滑动条,你应该会看到一个效果,向下面这样:
很酷吧? 我们也能做更多。在将其传递给fragment函数之前,让我们尝试更改这些顶点,在Inspector面板中暴露出一些结点属性。
Properties{ _Color("Color",Color)=(1,1,1,1) _MainTexture("Main Texture",2D)="white"{} _DissolveTexture("Dissolve Texture",2D)="white"{} _DissolveCuroff("Dissolve Cutoff",Range(0,1))=1 _ExtrudeAmount("Extrue Amount",float)=0 } ... float4 _Color; sampler2D _MainTexture; sampler2D _DissolveTexture; float _DissolveCutoff; float _ExtrudeAmount;
我们还将使用模型中的法线信息,因此,让我们添加这个字段到a2v的结构体中,以至于我们能访问它。
struct a2v{ float4 vertex:POSITION; float2 uv:TEXCOORD0; float3 normal:NORMAL; };
现在,添加一个单行到顶点函数中:
v2f vertexFunction(a2v v){ v2f o; v.vertex.xyz+=v.normal.xyz*_ExtrudeAmount; o.position=UnityObjectToClipPos(v.vertex); o.uv=v.uv; return o; }
我么在这里做的是,在将顶点转换为局部模型空间之前,我们将通过增加他们的法线方向时间来抵消它们的外加量,法线是一个向量代表顶点面向的方向,现在保存并返回Unity中,改变"Extrude Amount"的值,你应该看到下面这样的效果:
我们也能为这些属性制作动画:
v2f vertexFunction(a2v v){ v2f o; v.vertex.xyz+=v.normal.xyz*_ExtrudeAmount*sin(_Time.y); o.position=UnityObjectToClipPos(v.vertex); o.uv=v.uv; return o; }
_Time是一个代表时间的变量被包含在UnityCH.cginc中,y值代表秒,确保“Animated Materials” 在场景视图中被勾选,如下:
下面是我们最终的代码:
Shader "Unlit/Tutorial_Shader"{ Properties{ _Color("Color",Color)=(1,1,1,1) _MainTexture("Main Texture",2D)="white"{} _DissolveTexture("Dissolve Texture",2D)="white"{} _DissolveCutoff("Dissolve Cutoff",Range(0,1))=1 _ExtrudeAmount("Extrue Amount",float)=0 } Subshader{ Pass{ CGPROGRAM #pragma vertex vertexFunction #pragma fragment fragmentFunction #include "UnityCG.cginc" struct a2v{ float4 vertex:POSITION; float2 uv:TEXCOORD0; float3 normal:NORMAL; }; struct v2f{ float4 position:SV_POSITION; flaot2 uv:TEXCOORD0; }; float4 _Color; sampler2D _MainTexture; sampler2D _DissolveTexture; float _DissolveCutoff; float _ExtrudeAmount; v2f vertexFunction(a2v v){ v2f o; v.vertex.xyz+=v.normal.xyz*_ExtrudeAmount*sin(_Time.y); o.position=UnityObjectToClipPos(v.vertex); o.uv=v.uv; return o; } fixed4 fragmentFunction(v2f i):SV_TARGET{ float4 textureColor=tex2D(_MainTexture,i.uv); float4 dissolveColor=tex2D(_DissolveTexture,i.uv); clip(dissolveColor.rgb-_DissolveCutoff); return textureColor; } ENDCG } } }
第七部分:Scripting 和Shaders
接下来,我们将讨论怎样使用Unity脚本来控制Shader(即C#和Shader的交互),例如,我们将再次使用之前添加的_Color属性。首先,我们再片元函数中让其为着色器的颜色进行着色,如下:
fixed4 fragmentFunction(v2f i):SV_TARGET{ float4 textureColor=tex2D(_MainTexture,i.uv); float4 dissolveColor=tex2D(_DissolveTexture,i.uv); clip(dissolveColor.rgb-_DissolveCutoff); return textureColor*_Color; }
我们将输出颜色和_Color属性相乘,在Editor中如下:
现在,让我们开始写脚本吧,我们将为每一个对象添加一个名为RainbowColour.cs的脚本。
在脚本中,声明两个私有变量 Rendeerer和Materail:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class RainbowColor:MonoBehaviour{ Renderer rend; Material material; void Start(){ } void Update(){ } }
在Satrt()函数中,设置引用值。
void Start(){ rend=GetComponent<Renderer>(); material=rend.material; }
我们将在shader中使用Material.SetColor()函数设置颜色值,这个函数的第一个参数是一个字符串,它的名字使我们想要设置的属性的名字,第二个参数是我们想要设置的颜色的值。
void Start(){ rend=GetComponent<Renderer>(); material=rend.material; material.SetColor("_Color",Color.mangenta); }
当我们运行游戏的时候,颜色变为品红。
第八部分:阴影? 表面着色器?
到目前为止,我们写了一个Unlit Shader(无光照着色器),Unity还允许你写表面着色器,表面着色器实际上就像vertex/fragment着色器,除了它们去掉了许多使着色器与光照与阴影交互的示例代码。如果你对写光照和阴影感兴趣,这是一份很棒的教程 here
在这个章节,我将展示的是,表面着色器的每个部分如何与我们的顶点/片元着色器相关联,如果你在Unity中创建一个新的“Standard Shader",你会看到一些自动生成的代码,如下:
Shader "Custom/NewSurfaceShader" { Properties{ _Color("Color",Color)=(1,1,1,1) _MainTex("Albedo(RGB)",2D)="white"{} _Glossiness("Smothness",Range(0,1))=0.5 _Metallic("Metallic",Range(0,1))=0.0 } SubShader{ Tags{"RenderType="Opaque"} LOD 200 CGPROGRAM //基于物理着色的光照模型,并且在所有光类型上启用阴影 #pragma surface surf Standard fullforwardshadows //使用3.0着色器目标,获得更好的光照效果 #pragma target 3.0 sampler2D _MainTex; struct Input{ float2 uv_MainTex; }; half _Glossiness; half _Metrllic; fixed4 _Color; //为证着色器添加实例化支持,你需要在材质上检测“启用示例" UNITY_INSTANCING_CBUFFER_START(Props) UNITY_INSTANCING_CBUFFER_END void surf(Input in,inout SurfaceOutputStandard o){ fixed4 c=tex2D(_MainTex,in.uv_MainTex)*_Color; o.Albedo=c.rgb; o.Metallic=_Metallic; o.Smoothness=_Glossiness; o.Alpha=c.a; } ENDCG } FallBack "Diffuse" }
让我们看一看每一个部分并解释一下他们都做了什么。
首先,tags标签:SubShader{ Tags{"RenderType"="Opaque"} ... }
标签帮助你告诉渲染引擎如何以及何时你的着色器被渲染。在这种情况下,我们只是指定我们的着色器是透明的,这个对于深度纹理/地图是非常有用的。
LOD 200
多细节层次或者(LOD)有助于指定再默写硬件上使用哪种着色器,LOD值越大,着色器越复杂且它的值与模型的LOD无关。
#pragma surface surf Standard fullforwardshadows
类似于我们定义顶点和片元函数,我们在这里定义了一个称之为surf的表面函数,Stadard指定Unity Shader使用标准光照模型,而fullforwardshadows指定着色器启用所有常规阴影类型。
#pragma target 3.0
这里指定编译使用的光照版本,值越大,效果越好,也越复杂,同时对系统有更高的要求。
void surf(Input i,inout SurfaceOutputStandard o){ fixed4 c=tex2D(+mAINtEX,i.uv_MianT)*_Color; o.Albedo=c.rgb; o.Metallic=_Metallic; o.Smoothness=_Glossiness; o.Alpha=c.a; }
这是着色器的核心部分,Unity定义了一个SurfaceOutputStandard 结构体来替代指定像素的颜色值。你可以设置一些诸如“Albedo"的属性,由于我们正在处理光照和阴影,不单单是直接获取颜色值,需要能够通过SurfaceOutputStandard保存的值来进行计算,下面是SurfaceOutputStandard的所有属性值的一部分:
struct SurfaceOutput{ fixed3 Albedo; fixed3 Normal; fixed3 Emission; half Specular; fixed Gloss; fixed Alpha; }
Okay,讨论一下关于verties把。
standard surface 默认情况下不暴露编辑vertices属性的函数,我们可以手动添加一个。首先,添加pragma并定义一个vertex函数#pragma surface surf Standard fullforwardshadows vertex:vert
定义vert函数:
void vert(inout appdata_full v){ v.vertex.xyz+=v.normal.xyz*_ExtrudeAmount*sin(_Time.y; ) }
提示:如果你在改变顶点坐标的时候,阴影没有随之改变,你需要确保添加了”addshadow“ paagma 片段声明,如下:
#pragma surface surf Standard fullforwardshadows vetex:vert addshadow
再表面着色器的内部是非常复杂的,但是,它最终会被编译成我们之前写的顶点和片元函数那样。我强烈的建议去读官方文档,以了解更多关于这方面的信息。
更多内容,欢迎关注公众号:
-
OpenGL-Shader
2017-07-09 22:06:201.基础章节,从Shader1.0版本到新的4.5版本,介绍每一个版本中特性的用法; 2.Tesslattion Shader应用/基础案例分析 3.Gemotry Shader应用/基础案例分析 4.Compute Shader应用/基础案例分析 5.通过大量案例讲解分析... -
Vertex Shader
2019-02-04 19:30:50Vertex Shader一、Vertex Shader简介
前面一直有出现Vertex Shader,但Vertex Shader一直犹抱琵琶半遮面的感觉,本文将彻底揭开其面纱,看个清清楚楚、明明白折,以满足自己的好奇心。Vertex Shader在OpenGL ES 2.0的管道中的位置如下图所示:
本节将描述以下内容:
1)Vertex Shader的输入和输出
2)Vertex Shader的可移植性问题
3)Vertex Shader的例子
二、Vertex Shader的输入/输出和操作对象
Vertex Shader的操作对象是“顶点”。其输入/输出如下图所示:
Vertex Shader输入:
• Attributes:通过Vertex Arrays提供的每个顶点的数据
• Uniforms:由Vertex Shader使用的常量数据Vertex Shader程序:
• Shader program:Vertex Shader的可执行程序,它描述将在顶点上执行的操作Vertex Shader输出:
• varying变量:Vertex Shader的输出叫做varying变量,它有以下两个用处:
1)用于计算产生fragment
2)作为Fragment Shader的输入
三、Vertex Shader的内嵌变量
它包括以下几种内嵌变量:
• 特别变量:被Vertex Shader作为输出
• uniform状态:如深度范围
• 指定最大值的常量:如attribute个数、varying个数、uniforms个数
1. 特殊变量(Built-In Special Variables)
它作为Vertex Shader的输出、且作为Fragment Shader的输入,可用特别变量如下:
• gl_Position:它以裁剪坐标的方式输出“顶点位置”,且是一个highp变量。
• gl_PointSize:它以像素为单位输出“点的大小”,其值被限制在OpenGL ES 2.0可实现的范围内,且是一个mediump变量。
• gl_FrontFacing:它不直接由Vertex Shader写,是一个boolean变量。
2. Uniform状态变量(Built-In Uniform State)
• gl_DepthRange:是Vertex Shader中唯一的一个Uniform状态变量,它以窗口坐标来表示深度范围。其数据类型如下:
struct gl_DepthRangeParameters {
highp float near; // near Z
highp float far; // far Z
highp float diff; // far - near
}uniform gl_DepthRangeParameters gl_DepthRange;
3. 常量(Built-In Constants)
Vertex Shader中指定最大值的常量如下:
const mediump int gl_MaxVertexAttribs = 8;
const mediump int gl_MaxVertexUniformVectors = 128;
const mediump int gl_MaxVaryingVectors = 8;
const mediump int gl_MaxVertexTextureImageUnits = 0;
const mediump int gl_MaxCombinedTextureImageUnits = 8;1)gl_MaxVertexAttribs
指示支持的Vertex Attribute的最大值,其值最小为8。
2)gl_MaxVertexUniformVectors
指示Vertex Shader最多支持多少个vec4 uniform,其值最小为128。
3)gl_MaxVaryingVectors
Vertex Shader最多可以输出多少个vec4变量,其值最小为8。
4)gl_MaxVertexTextureImageUnits
Vertex Shader最多支持多少个纹理单元,其值最小为0,即不支持顶点纹理获取。
5)gl_MaxCombinedTextureImageUnits
Vertex Shader和Fragment Shader总共最多支持多少个纹理单元,其值最小为8。
在具体的硬件平台上,可通过以下代码获取当前可支持的最大值:
GLint maxVertexAttribs, maxVertexUniforms, maxVaryings; GLint maxVertexTextureUnits, maxCombinedTextureUnits; glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs); glGetIntegerv(GL_MAX_VERTEX_UNIFORM_VECTORS, &maxVertexUniforms); glGetIntegerv(GL_MAX_VARYING_VECTORS, &maxVaryings); glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS,&maxVertexTextureUnits); glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,&maxCombinedTextureUnits);
4. 精度限制 (Precision Qualifiers)
在Vertex Shader中,如果没有指定默认的精度,则float和int的精度为highp。在Vertex Shader中,除color和lighting计算为mediump之外,其它的都为highp。
四、OpenGL ES 2.0 Vertex Shader限制
为了让Shader Source可以大部分平台上进行运行(即可移植性好),需要参加以下限制。
1. Vertex Shader长度
目前没有办法获得具体平台可支持的最大指令数,但如果Shader source超过其限制,则编译会失败。
2. 临时变量(函数内局部变量)的个数
也没有指定最大数,但如果Shader source超过其限制,则编译会失败。
3. 流程控制
• 对于for循环,有以下限制:
1)只能有一个循环变量
2)循环变量必须被常量表达式初始化
3)条件判断必须为下面的一种:
loop_indx < constant_expression loop_indx <= constant_expression loop_indx > constant_expression loop_indx >= constant_expression loop_indx != constant_expression loop_indx == constant_expression
4)循环变量必须通过以下方式来进行修改,且只能在循环表达式内部修改,不能在循环体内修改
loop_index-- loop_index++ loop_index -= constant_expression loop_index += constant_expression
5)循环变量在循环体内部,只能做为函数的只读参数。
注:while和do-while循环不支持。• 对于if-else判断语句,其条件判断bool_expression必须为scalar boolean value。
再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow
-
Unity Shader 之Geometry Shader
2017-06-19 10:59:09其实unity shader除了vertex shader和frag shader之外,还有一个geom shader,处理流程在vextex shader和frag shader之间,主要是能获取到一个顶点临近的一些顶点,处理三角形之类的,下面看一个例子 Shader ...其实unity shader除了vertex shader和frag shader之外,还有一个geom shader,处理流程在vextex shader和frag shader之间
首先 [maxvertexcount(num)]这个必须写在geomshader前面,是不可少的,主要是定义输出顶点的最大数量,输出顶点可以每次都不同,但是不超过这个数就行,注意这个数不要太大,影响性能哦
然后geom shader里面有两个参数,一个输入的一个输出的,(其实不止两个,不过大部分情况下只考虑这两种就可以了),输入参数有下面几种
1:point 输入图元为点
2:line 输入图元为线
3:triangle 输入图元为三角形
4:lineadj:输入图元为带有邻接信息的直线,由4个顶点构成4条线
5:triangleadj:输入图元为带有邻接信息的三角形,由6个顶点构成
输出类型
PointStream<OutputVertexType>
LineStream<OutputVertexType>
TriangeStream<OutputVertexType>
当你希望输出一个三角形列表时,应该没输出三个顶点就执行一次triangeStream.RestartStrip();
下面是一个例子
Shader "Custom/GeomShader" { Properties { _MainTex("Texture", 2D) = "white" {} } SubShader { Tags{ "RenderType" = "Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma geometry geom #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float2 uv : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; float3 normal : NORMAL; float2 uv : TEXCOORD0; float3 worldPosition : TEXCOORD1; }; sampler2D _MainTex; v2f vert(appdata v) { v2f o; o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); o.uv = v.uv; o.normal = v.normal; o.worldPosition = mul(unity_ObjectToWorld, v.vertex).xyz; return o; } [maxvertexcount(3)] //表示最后outputStream中的v2f数据是3个 void geom(triangle v2f input[3], inout TriangleStream<v2f> OutputStream) { v2f test = (v2f)0;//这里直接重构v2f这个结构体,也可以定义v2g,g2f两个结构体来完成这个传递过程 float3 normal = normalize(cross(input[1].worldPosition.xyz - input[0].worldPosition.xyz, input[2].worldPosition.xyz - input[0].worldPosition.xyz)); for (int i = 0; i < 3; i++) { test.normal = normal; //顶点变为这个三角图元的法线方向 test.vertex = input[i].vertex; test.uv = input[i].uv; OutputStream.Append(test); } } fixed4 _LightColor0; fixed4 frag(v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); float3 lightDir = -_WorldSpaceLightPos0.xyz; float ndotl = dot(i.normal, normalize(lightDir)); return _LightColor0 * ndotl*col; } ENDCG } } }
效果图对比,之前
之后:
-
【Unity3D Shader编程】之十三 单色透明Shader & 标准镜面高光Shader
2016-03-13 16:48:12本次更新放出的Shader为透明系列的3个Shader和标准的镜面高光Shader的两个Shader。由易到难,由入门级到应用级,难度梯度合理。 依然是先放出游戏场景的exe和运行截图。 本期用的模型为妙蛙草。 【可运行... -
compute shader
2018-01-12 18:11:45computer shader -
Unity Shader - Built-in shader include files 内置shader头文件
2019-06-21 18:45:58目录:Unity Shader - 知识点目录(先占位,后续持续更新) 原文:Built-in shader include files 版本:2019.1 Built-in shader include files 内置shader头文件 Unity包含一些文件可用于给你的 shader programs ... -
着色器Shader及Shader的图像处理,Shader Demo
2019-07-07 01:22:30android-gpuimage里给出了很多滤镜效果,本质都是用shader处理图像。 使用OpenGL做图像处理,最主要的就是FragShader的实现,以下是几种已经实现了的shader。有黑白照处理,Sephia特效,反相,高斯模糊,Median模糊... -
Shader|什么是Shader
2018-10-12 18:01:38Shader所在的阶段就是GPU渲染流水线的一部分。要得出出色的游戏画面是需要包括Shader在内的、CPU在内的所有的渲染流水线的共同参与才可完成:设置适当的渲染状态、使用混合函数或者开/关闭深度测试等。 具体的... -
【Unity3D Shader编程】之一 夏威夷篇:游戏场景的创建 & 第一个Shader的书写
2014-11-03 02:24:52作为一个系统介绍Unity3D中Shader编写的系列文章的开篇,本文的第一部分系列文章的前言,然后第二部分介绍了这个系列文章中我们会使用的游戏场景创建方式,最后一部分讲解了如何在Unity中创建和使用Shader,为后面... -
UnityShader与Shader的区别
2018-10-24 17:21:26一、UnityShader与Shader 在Unity里,Unity Shader实际上指的就是一个ShaderLab文件——硬盘上以.shader作为文件后缀的一种文件。 Unity Shader相比于Shader,有优点也有缺点。 优点有: 在传统的... -
Unity Shader - Making multiple shader program variants 制作shader程序多变体
2019-06-24 19:26:27目录:Unity Shader - 知识点目录(先占位,后续持续更新) 原文:Making multiple shader program variants 版本:2019.1 Making multiple shader program variants 制作shader程序多变体 通常它将方便保留大部分... -
Shader与ShaderToy开篇
2018-07-24 10:49:46写在前面 Unity从学习到工作至今已经有三个年头了,开发中每次遇到难点第一件事就是百度搜索,从来没有自己的想法,甚是惭愧。想着不能再这样浑浑噩噩下去... 说起来跟Shader也很有渊源,因为以前自学过C#,第一... -
shader实例(unity内置shader)
2019-05-20 11:12:04熟悉Unity3d内置shader应该算是学习unity shader不可缺少的一步,Unity3d自带60多个shader,这些shader被分为五大类Normal,Transparent,Transparent,Cutout,Self-llluminated,Reflective。 (一) Normal Shader ... -
【Unity3D Shader编程】之二 雪山飞狐篇:Unity的基本Shader框架写法&颜色、光照与材质
2014-11-09 23:24:39本篇文章中,我们学习了Unity Shader的基本写法框架,以及学习了Shader中Properties(属性)的详细写法,光照、材质与颜色的具体写法。写了6个Shader作为本文Shader讲解的实战内容,最后创建了一个逼真的暴风雪场景... -
UnityShader
2017-08-18 15:00:13Unity Shader 手册本文将逐篇翻译Unity-Manual:Shader Reference的内容。Unity手册索引: 写surface shader Surface shader示例 在surface shader中自定义光照模型 Surface shader光照示例 Surface shader中使用 DX... -
Unity Shader 自定义SurfaceShader
2017-12-15 18:13:48这里只做记录用,不进行...Shader "Custom/SurfaceShader" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _Glossiness ("Smoothness", Range(0,1)) = 0.5 -
shader插件学习AmplifyShaderEditor
2018-06-01 21:45:19lerp(a,b,c): a(1-c)+bc图片的lerp shader editor: 按键5+鼠标左键 新建颜色 右键搜索想要一个物体正反面都渲染 比如古风人物大袖子里外效果不同 有三种方法:1.插件写shader2.改shader源码 流程: unity官网... -
【Shader】Shader官方示例
2018-12-20 11:02:54Surface Shader示例 在表面着色器 。此页面上的示例显示如何使用内置照明模型。有关如何实现自定义光照模型的示例,请参阅Surface Shader光照示例。 简单着色器 例 我们将从一个非常简单的Shader开始,并在此基础上... -
【Unity Shader编程】之十四 边缘发光Shader(Rim Shader)的两种实现形态
2016-06-26 20:42:33这篇文章主要讲解了如何在Unity3D中分别使用Surface Shader和Vertex & Fragment Shader实现边缘发光Shader。 一、最终实现的效果 边缘发光Shader比较直观的一个运用便是模拟宇宙中的星球效果。将本文实现的边缘... -
Shader Stages
2015-11-20 17:54:02The Direct3D 10 and higher pipeline contains three programmable-shader stages (the rounded blocks in the pipeline functional diagram). Each of these shader stages exposes its own unique ... -
UnityShader——初探Compute Shader
2017-07-20 16:43:38Compute Shader是基于DX11(SM4.5+)的在GPU上运行的程序,通过Compute Shader我们可以将大量可以并行的计算放到GPU中计算从而节省CPU资源,Unity 5.6版本提供的 Graphics.DrawMeshInstancedIndirect 接口可以非常... -
【Shader Graph】Shader Graph入门
2019-09-27 18:15:54Unity2018引用了ShaderGraph:通过可视化界面拖拽就可以实现着色器的创建和编辑,听起来很简单,那我们一起来尝试下吧。 我用的unity版本为2019.1.0f2 一.如何引入ShaderGraph? 方法有两种: 1.新工程: 新建... -
UnityShader入门 积雪Shader
2016-05-26 23:09:15积雪Shader 如果你是一个shader编程的新手,并且你想学到下面这些酷炫的技术,我觉得你可以看看这篇教程: 实现一个积雪效果的shader创建一个具有凹凸纹理的shader为每个像素修改其对应纹理值在表面着色... -
Tessellation Shader
2017-01-05 17:10:50Tessellation Control Shader(TCS): Tessellation Evaluation Shader(TES):Tessellation Shader的GLSL入门实现: 曲线 Tessellation Shader的GLSL入门实现: 平面 OpenGL的各个Shader的作用与区别 -
Shader2D: 一些2D效果的Shader实现
2017-04-09 01:59:37Shader2D: 一些2D效果的Shader实现 包括:模糊,锐化,圆形裁剪,正六边形裁剪,圆角,UV动画,百叶窗,马赛克,浮雕,铅笔画,水彩画,灰化,老照片,饱和度,HDR,内发光,外发光,扭曲,旋涡,波浪,水滴散开等 -
Unity Shader 二 发光Shader
2016-04-17 19:46:17哈哈,我又回来了,自从上篇的特效Shader之后,这是这个系列的第二弹。老实说这次想写的内容,我也考虑了很久,最终还是将内容暂定为使用的频率较多的外发光。其实外发光可以说是一个烂大街的Shader了,网上也有很多... -
Koo叔说Shader—Shader简介
2018-05-17 22:16:39Shader的介绍有很多,在这里,将从以下几个方面来介绍: - 什么是Shader - Shader的主要作用 - Shader的种类 - Shader的结构 什么是Shader Shader的意思是着色器,是运行在GPU上的小程序。这些小程序为图形...
-
gradle-5.x.all.zip,懂的下载,保证你满意而归
-
java 获取HTML字符串的所有的img图片链接
-
The-Python-3-Standard-Library-by-Example.rar
-
转行做IT-第1章 计算机基础
-
微信小程序——日历组件:calendar:。
-
前端性能优化
-
消息中间件(2)MQ使用场景(解耦,异步消息,流量削锋)
-
单片机完全学习课程全五季套餐
-
乐优商城(三)
-
转行做IT-第2章 HTML入门及高级应用
-
JDK8中 Arrays.sort 底层排序算法的选择
-
RabbitMQ消息中间件实战(附讲义和源码)
-
tendisplus-2.1.2-rocksdb-v5.13.4.zip
-
9、Android 自定义对话框
-
安卓已死?Android事件分发机制及设计思路,送大厂面经一份!
-
0002简历.doc
-
JavaEE框架(Maven+SSM)全程实战开发教程(源码+讲义)
-
常用正则化方法matlab代码及说明
-
一个致力于微信小程序和Web端同构的解决方案
-
OSError: Unable to open file (file signature not found)