欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

浅谈Unity中的Shader

程序员文章站 2022-06-19 08:19:04
一、shader基础知识1.1、什么是shader在讲什么是shader之前我们先看看下面两段代码 这两段代码实现的功能都是提取 2d 图像上每个像素点的颜色值,第一段代码是用c++写的,在...

一、shader基础知识

1.1、什么是shader

在讲什么是shader之前我们先看看下面两段代码 

浅谈Unity中的Shader

这两段代码实现的功能都是提取 2d 图像上每个像素点的颜色值,第一段代码是用c++写的,在cup上面运行,它需要循环遍历每个像素点,第二段代码是cg代码,在gpu上面运行,它只需要一行代码就能实现同样的功能。gpu是专门用来进行图形处理的,而shader,就是gpu执行的一段针对3d对象进行操作的程序。

*上对shader的解释是这样

shader(着色器)应用于计算机图形学领域,指一组供计算机图形资源在执行渲染任务时使用的指令,用于计算图像的颜色或明暗。但近来,它也能用于处理一些特殊效果,或者视频后处理。通俗地说,着色器告诉电脑如何用特有的一种方法去绘制物体。

程序员将着色器应用于图形处理器(gpu)的可编程流水线,来实现三维应用程序。这样的图形处理器有别于传统的固定流水线处理器,为gpu编程带来更高的灵活性和适应性。以前固有的流水线只能进行一些几何变换和像素灰度计算。现在可编程流水线还能处理所有像素、顶点、纹理的位置、色调、饱和度、明度、对比度并实时地绘制图像。着色器还能产生如模糊、高光、有体积光源、失焦、卡通渲染、色调分离、畸变、凹凸贴图、边缘检测、运动检测等效果。

1.2、opengl的渲染流程

知道了什么是shader,我们再来了解一下shader的种类,首先我先介绍一下opengl的渲染流程

浅谈Unity中的Shader

上图便是opengl的渲染流程,将这个流程简化之后是这样的 

顶点变换 → 图元装配和光栅化 → 片元纹理映射和着色 → 写入帧缓存

在顶点变换和片元着色这两步时,我们就可以对其编程,进行各种操作,其他的部分我们是没法进行编程的。我们的shader就是作用于顶点变换和片元着色这两个部分的。

1.3、shader的种类

知道了shader起作用的地点,我们现在可以了解一下shader的种类了。

shader按管线分类一般分为固定渲染管线与可编程渲染管线。固定渲染管线就是功能固定的管线,比如物体表面光的折射,反射的算法是固定无法修改的,我们只能对这些功能进行配置,比如开启或关闭反射效果,雾化效果等。因为这种管线功能固定,无法在程序上对物体细节的表现给予更多更*的控制,无法达到更多我们想要的画面效果。所以现在的显卡都是可编程渲染管线,也就是曾经那些我们固定无法修改的部门现在可以编程去修改,*度高了之后,我们也就能实现更多自己想要的特效了。

1.4、shader的开发语言

知道了shader的种类,我在来说说shader的开发语言

hlsl: 主要用于direct3d。平台:windows。

glsl: 主要用于opengl。 平台:移动平台(ios,安卓),mac(only use when you target mac os x or opengl es 2.0)

cg:与directx 9.0以上以及opengl 完全兼容。运行时或事先编译成gpu汇编代码。cg比hlsl、glsl支持更多的平台,unity shader采用cg/hlsl作为开发语言。

二、unity中shader知识介绍

2.1、shader在gpu的渲染流程

浅谈Unity中的Shader

进入gpu运算首先进行的是vertex processor顶点处理器,这个部分就需要我们使用vertex shader顶点着色器,顶点着色器运算的结果会交给pixel processor像素处理器,也就是片段处理器,在这个部分我需要为像素处理编写pixel shader像素着色器程序,这部分计算完后就输出了最终我们可以用于在屏幕上的颜色信息,我们把它叫做frame buffer帧缓冲。帧缓冲存储的是计算机依次显示所要的数据。

2.2、unity中shader的类型

①fixed function shader :属于固定渲染管线 shader, 基本用于高级shader在老显卡无法显示时的回滚。使用的是shaderlab语言,语法与微软的fx files 或者nvidia的 cgfx类似。

②vertex and fragment shader:最强大的shader类型,属于可编程渲染管线. 使用的是cg/hlsl语法。

③surface shader:unity3d推崇的shader类型,使用unity预制的光照模型来进行光照运算。使用的也是cg/hlsl语法。

我们先了解一下这三种shader的异同点。

相同点: 

①都必须从唯一一个根shader开始 
②properties参数部分,作用及语法完全相同 
③具体功能都在subshader里(subshader会自上而下运行第一个硬件能支持的) 
④subshader都可以打标签 
⑤都可以回滚 
⑥都可以处理基本的功能,例如光照漫反射(diffuse)以及镜面反射(specular)。但是vertex and fragment和surface都能实现fixed function实现不了的高级功能,例如基于uv计算的效果等等。

不同点 

①fixed function shader以及vertex and fragment shader在subshader下面还有pass{}结构,但是surface shader,已经将具体内容打包在光照模型了,不能加pass{} 
②fixed function shader每句代码之后没有分号“;”, 但是v&f shader以及surface shader每句代码之后都必须加分号“;” 
③核心结构不同 

fixed function shader的sunshader中的结构为

material{} 
……
settexture[_maintex]{
    ……
}

vertex and fragment shader的核心结构为

cgprogram
#pragma vertex vert
#pragma fragment frag   
……       
#include "unitycg.cginc"
endcg

surface shader的核心结构是

cgprogram
#pragma surface surf lambert
……
endcg

可以看到这三种shader的subshader内的编码实现是不一样的,这三种shader的结构如下图,他们的不同点都在subshader里面 

浅谈Unity中的Shader

因为unity推荐surface shader,所以文章直接分析surface shader的用法,其他两种shader就不做过多介绍了。

三、surface shader语法

在unity的项目面板中直接创建一个stander surface shader,默认生成的代码如下

shader "custom/diffuseshader" {
    properties {
        _color ("color", color) = (1,1,1,1)       //设置一个默认的颜色值
        _maintex ("albedo (rgb)", 2d) = "white" {}  //默认的白色纹理
        _glossiness ("smoothness", range(0,1)) = 0.5  //默认的光泽度
        _metallic ("metallic", range(0,1)) = 0.0    //金属光泽度
    }
    subshader {
        tags { "rendertype"="opaque"}
        lod 200
 
        cgprogram
        // physically based standard lighting model, and enable shadows on all light types
        #pragma surface surf standard fullforwardshadows
 
        // use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0
 
 
        fixed4 _color;
        sampler2d _maintex;
        half _glossiness;
        half _metallic;
 
 
        struct input {
            float2 uv_maintex;
        };
 
 
 
        void surf (input in, inout surfaceoutputstandard o) {
            // albedo comes from a texture tinted by color
            fixed4 c = tex2d (_maintex, in.uv_maintex) * _color;
            o.albedo = c.rgb;
            // metallic and smoothness come from slider variables
            o.metallic = _metallic;
            o.smoothness = _glossiness;
            o.alpha = c.a;
        }
        endcg
    }
    fallback "diffuse"
}

接下来我来介绍一下这段代码

properties {}

properties{}是定义着色器属性的,在这里定义的属性将被作为输入提供给所有的子着色器。属性定义的格式如下

_name(“display name”, type) = defaultvalue[{options}]

_name代表的是属性名,如color,maintex,glossiness ,metallic 等

”display name”则是在inspector中显示的名字

type代表属性:

color - 一种颜色,由rgba(红绿蓝和透明度)四个量来定义;

2d - 一张2的阶数大小(256,512之类)的贴图。这张贴图将在采样后被转为对应基于模型uv的每个像素的颜色,最终被显示出来;

rect - 一个非2阶数大小的贴图;

cube - 即cube map texture(立方体纹理),简单说就是6张有联系的2d贴图的组合,主要用来做反射效果(比如天空盒和动态反射),也会被转换为对应点的采样;

range(min, max) - 一个介于最小值和最大值之间的浮点数,一般用来当作调整shader某些特性的参数(比如透明度渲染的截止值可以是从0至1的值等);

float - 任意一个浮点数;

vector - 一个四维数;

这段默认properties在inspector中的显示效果如下 

浅谈Unity中的Shader

subshader{}

tags :tags标签是三种类型的shader都具有的标签,它决定了硬件什么调用该子着色器

tags标签里面默认的“rendertype”=”opaque”,是告诉系统应该在渲染非透明物体时调用这个subshader

“rendertype”=”transparent”表示在渲染含有透明效果的物体时调用该sunshader,

tags里面还有许多其他的我们可选的标签

①.”queue”:定义渲染顺序。预制的值有这些 

    ”background”。值为1000。比如用于天空盒。

    ”geometry”。值为2000。大部分物体在这个队列。不透明的物体也在这里。

    ”alphatest”。值为2450。已进行alphatest的物体在这个队列。 

    ”transparent”。值为3000。透明物体。 

    ”overlay”。值为4000。比如镜头光晕。 

    用户可以定义任意值,比如”queue”=”geometry+10”

②“rendertype”:定义渲染类型。预制的值有这些

    ”opaque”:绝大部分不透明的物体都使用这个; 

    ”transparent”:绝大部分透明的物体、包括粒子特效都使用这个; 

    ”background”:天空盒都使用这个; 

    ”overlay”:gui、镜头光晕都使用这个; 

③”forcenoshadowcasting”:定义物体是否有阴影效果

    “true”。表示有阴影

    “false”。表示没有阴影

lod:level of detail的缩写,它表示着色器的细节层次效果。在某些硬件比较差的系统上,我们可以设置一个低一点的值,减少细节的显示。unity内置shader的lod值如下

  • 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

从cgprogram 到endcg这一部分就这这个shader的核心内容了

#pragma surface surf standard fullforwardshadows

这段编译指令声明了我们要写一个surface shader,并指定了光照模型。它的写法是这样的

#pragma surface surfacefunction lightmodel [optionalparams]

surface - 声明的是一个表面着色器surfacefunction - 着色器代码的方法的名字lightmodel - 使用的光照模型。

这段代码默认的surfacefunction为surf,我们可以在源码的底部看到在这儿声明了的surf函数。默认的lightmodel为standard。

下面我先介绍一下lightmodel光照模型

  • lambert:该光照模型能很好的表示粗糙表面的光照,但不能表现出镜面反射高光
  • toon:最近在游戏中常用的风格之一即是toon shading(又称 cel shading).这是一种非逼真渲染风格,通过改变了光在一个模型上反射实际情况来给人以手绘的感觉
  • blinnphong:仿真镜面反射材料
  • standard:unity5中默认的光照模式是standard, 其引入了 物理渲染 (pbr), 但是与其它光照模型没有什么不同。相比于朗伯反射, pbr提供了一个更加逼真的光线物体作用模型,pbr考虑了材料的物理属性, 比如能量守恒以及光的散射

接下来的这段代码

fixed4 _color;
sampler2d _maintex;
half _glossiness;
half _metallic;

我们可以发现 _color,_maintex,_glossiness,_metallic都shader属性的声明,在上面的properties 中已经声明过了这些属性,但是在这段cg程序,要想访问在properties中所定义的变量的话,必须使用和之前变量相同的名字再次进行声明,其实就是链接在上面properties中声明的属性。

我再来介绍一下shader中常用的数据类型

3种基本数值类型:float、half和fixed。 
这3种基本数值类型可以再组成vector和matrix,比如half3是由3个half组成、float4x4是由16个float组成。

float:32位高精度浮点数。 
half:16位中精度浮点数。范围是[-6万, +6万],能精确到十进制的小数点后3.3位。 
fixed:11位低精度浮点数。范围是[-2, 2],精度是1/256。 
sampler2d:2d纹理属性

接下来就是input结构体

struct input {
    float2 uv_maintex;
};

这个结构体和surf函数中的另一个参数inout结构体是相对的,一个代表输入,一个代表输出。我们可以这样理解这两个结构体,你定义输入数据结构(inputs struct)、编写自己的surface函数处理输入、最终输出修改过后的surfaceoutput。input其实是需要我们去定义的结构,所以我们可以把所需要参与计算的数据都放到这个input结构中,传入surf函数使用

默认的input结构体中有一个uv_maintex参数,代表了纹理的uv值,我们便可以在surf函数中直接使用这个参数了。

知道了input的结构体,我们在来看看output的结构体

struct surfaceoutput
{
    fixed3 albedo;  // diffuse color  漫反射的颜色值。
    fixed3 normal;  // tangent space normal, if written 法线坐标
    fixed3 emission;  //自发光颜色
    half specular;  // specular power in 0..1 range 镜面反射系数
    fixed gloss;    // specular intensity   光泽系数
    fixed alpha;    // alpha for transparencies  透明度系数
};

现在我们在来看看surf函数里面的内容,就已经能够看懂了

我们现在在来看看surf函数里面的代码,就能知道里面是什么意思了

void surf (input in, inout surfaceoutputstandard o) {
    fixed4 c = tex2d (_maintex, in.uv_maintex) * _color;
    o.albedo = c.rgb;               //将物体显示的漫反射颜色设置成在纹理的颜色值
    o.metallic = _metallic;         //将物体显示的金属光泽设置成在properties中定义的光泽
    o.smoothness = _glossiness;     //设置物体显示的光滑度
    o.alpha = c.a;                  //设置物体显示的透明度
}

以上就是浅谈unity中的shader的详细内容,更多关于unity shader的资料请关注其它相关文章!

相关标签: Unity Shader