Unity+Shader入门精要-1. 入门shader

news2024/12/25 12:30:05

今天开始正式整合学习的shader内容。

Simple Shader

主要介绍了大概的shader格式。

Shader "Unity Sgaders Book/Chapter 5/Simple Shader" //shader名
{
    Properties
    {
        //声明color类型的属性
        _Color("Color Tint", Color) = (1.0,1.0,1.0,1.0)
    }
    SubShader
    {
        Pass
        {
            Tags{"LightMode" = "ForwardBase"}
            CGPROGRAM
            #pragma vertex vert //顶点着色器函数
            #pragma fragment frag //片元着色器函数
            fixed4 _Color; //定义color类型

            //模型空间的输入顶点信息
            struct a2v {
                float4 vertex:POSITION; //模型空间的顶点坐标
                float3 normal:NORMAL;//模型空间的法线方向
                float4 texcoord:TEXCOORD0;//模型的第一套纹理坐标
            };

            //齐次裁剪空间的输出顶点信息
            struct v2f {
                float4 pos : SV_POSITION;//裁剪空间的顶点坐标
                fixed3 color : COLOR0;//颜色信息
            };

            //输入模型空间的顶点信息,经过顶点着色器函数,输出齐次裁剪空间的顶点信息
            v2f vert(a2v v){
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.color = v.normal * 0.5 + fixed3(0.5, 0.5, 0.5);
                return o;
            }

            //输入齐次裁剪空间的顶点信息(经过插值之后),输出顶点颜色信息
            fixed4 frag(v2f i) : SV_Target
            {
                fixed3 c = i.color;
                c *= _Color.rgb;
                return fixed4(c,1.0);
            }
            ENDCG
        }
    }
}

其主要意义在于通过模型法线获取不同参数,从而在材质面板中(材质面板默认显示球体,球体上每一个顶点的法线都不一样)显示一个颜色拾取器。效果如下图所示:

False Color

假彩色图像(false-color image)用于可视化一些数据,以方便对shader进行调试。即将想要的数据映射到[0-1]区间,作为颜色输出到屏幕上,然后通过屏幕上显示的像素颜色来判断这个值是否正确。通常用于debug。shader代码如下:

Shader "Unity Sgaders Book/Chapter 5/False Color"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct v2f
            {
                float4 pos:SV_POSITION;
                fixed4 color : COLOR0;
            };
        
            v2f vert(appdata_full v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);

                //可视化法线方向
                o.color = fixed4(v.normal * 0.5 + fixed3(0.5,0.5,0.5),1);

                //可视化切线方向
                //o.color = fixed4(v.tangent * 0.5 + fixed3(0.5,0.5,0.5),1);
                
                //可视化第一组纹理坐标
                //o.color = fixed4(v.texcoord.xy, 0, 1);

                //可视化顶点颜色
                //o.color = v.color;

                return o;
            }

            //不知道作者为何在这里只输出一个fixed参数,即只利用了第一个颜色参数,输出出来的只有红色
            fixed frag(v2f i) :SV_Target
            {
                return i.color;
            }
            ENDCG
        }
    }
}

比如输出法线可视化,效果如下:

Diffuse Vertex Level/Diffuse Pixel Level

漫反射光照即是入射光线经法线后的反射光线的强度与“入射光线和发现之间的点积”有正比关系。颜色方面,需要叠加材质的漫反射颜色和入射光线的颜色。于是得出以下公式:

可以写下如下代码(逐顶点光照):

Shader "Unity Sgaders Book/Chapter 5/Diffuse Vertex Level"
{
    Properties
    {
        //声明color类型的属性
        _Diffuse("Diffuse", Color) = (1,1,1,1)
    }
    SubShader
    {
        Pass
        {
            Tags{"LightMode" = "ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            fixed4 _Diffuse;
            
            struct a2v
            {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
                fixed3 color : COLOR;
            };

            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);//模型空间坐标转换为齐次裁剪空间
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;//环境光照信息
                fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));//转换法线从模型空间转为世界空间
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);//标准化世界光照向量信息
                //光照公式,等于自发光+漫反射+环境光
                //这一步求漫反射
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
                //这一步求漫反射和环境光交互
                o.color = ambient + diffuse;
                return o;
            }

            fixed4 frag(v2f i) :SV_Target
            {
                return fixed4(i.color,1);
            }
            ENDCG
        }
    }
}

其中Diffuse为可调整的漫反射颜色。除了本身的漫反射计算以外,还需要考虑环境光的交互,所以需要加上环境光的颜色。

效果如下:

可以看到逐顶点光照的缺点就是有很明显的锯齿状,对于细分程度较低的模型会遇到。因此可以采用逐像素光照,即把color赋值的操作转移到frag函数中实现,在顶点着色器部分只处理和顶点有关的数据转换即可:

Shader "Unity Sgaders Book/Chapter 5/Diffuse Pixel Level"
{
    Properties
    {
        //声明color类型的属性
        _Diffuse("Diffuse", Color) = (1,1,1,1)
    }
    SubShader
    {
        Pass
        {
            Tags{"LightMode" = "ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            fixed4 _Diffuse;
            
            struct a2v
            {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
                float3 worldNormal:TEXCOORD0;
            };

            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);//模型空间坐标转换为齐次裁剪空间
                o.worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));//转换法线从模型空间转为世界空间
                return o;
            }

            fixed4 frag(v2f i) :SV_Target
            {
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;//环境光照信息
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);//标准化世界光照向量信息
                //光照公式,等于自发光+漫反射+环境光
                //这一步求漫反射
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(i.worldNormal, worldLight));
                //这一步求漫反射和环境光交互
                fixed3 color = ambient + diffuse;
                return fixed4(color,1);
            }
            ENDCG
        }
    }
}

效果如下:

可以看到光滑了很多。

Half Lambert

以上介绍的是兰伯特光照模型,使用max(0,dot(n,I))来保证点积为非负数。我们也同样可以做α倍的缩放和β的偏移,来让dot(n,I)从[-1,1]的范围映射到[0,1]的范围,如下公式:

绝大部分情况,α=β=0.5。

因此稍微修改之前的逐像素光照模型即可完成任务:

Shader "Unity Sgaders Book/Chapter 5/Half Lambert"
{
    Properties
    {
        //声明color类型的属性
        _Diffuse("Diffuse", Color) = (1,1,1,1)
    }
    SubShader
    {
        Pass
        {
            Tags{"LightMode" = "ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            fixed4 _Diffuse;
            
            struct a2v
            {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
                float3 worldNormal:TEXCOORD0;
            };

            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);//模型空间坐标转换为齐次裁剪空间
                o.worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));//转换法线从模型空间转为世界空间
                return o;
            }

            fixed4 frag(v2f i) :SV_Target
            {
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;//环境光照信息
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);//标准化世界光照向量信息
                //光照公式,等于自发光+漫反射+环境光
                //这一步求漫反射
                fixed halfLambert = saturate(dot(i.worldNormal, worldLight)) * 0.5 + 0.5;
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
                //这一步求漫反射和环境光交互
                fixed3 color = ambient + diffuse;
                return fixed4(color,1);
            }
            ENDCG
        }
    }
}

效果如下:

显而易见地观察到,它会比之前的光照模型要亮,是因为几乎没有diffuse=0的情况,几乎所有点都是亮的。

Specular Vertex Level/Specular Pixel Level

除了漫反射,还需要考虑高光反射,高光反射的计算公式如下:

即入射光的颜色和强度作用于带有高光属性的材质上,其与“视角方向和反射方向的点积”成正比关系。

 因此延续漫反射光照模型继续写下去,只是添加了高光数据,如下逐顶点光照:

Shader "Unity Sgaders Book/Chapter 5/Specular Vertex Level"
{
    Properties
    {
        _Diffuse("Diffuse", Color) = (1,1,1,1) //声明color类型的属性
        _Specular("Specular",Color) = (1,1,1,1) //高光反射颜色
        _Gloss("Gloss",Range(8,256)) = 20 //高光区域大小
    }
    SubShader
    {
        Pass
        {
            ......
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;
            ......

            v2f vert(a2v v)
            {
                ......
                
                //光照反射
                fixed3 reflectDir = normalize(reflect(-worldLightDir,worldNormal));//计算入射光关于法线的反射方向
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);//视角方向
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
                //这一步求漫反射和环境光和高光
                o.color = ambient + diffuse + specular;
                return o;
            }

            ......
        }
    }
}

同样有锯齿问题,于是引入逐像素光照,和diffuse pixel level类似,只需要在顶点着色器内部处理顶点相关信息,在片元着色器内部处理颜色信息即可:

Shader "Unity Sgaders Book/Chapter 5/Specular Pixel Level"
{
    Properties
    {
        _Diffuse("Diffuse", Color) = (1,1,1,1) //声明color类型的属性
        _Specular("Specular",Color) = (1,1,1,1) //高光反射颜色
        _Gloss("Gloss",Range(8,256)) = 20 //高光区域大小
    }
    SubShader
    {
        Pass
        {
            ......
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;
            ......

            fixed4 frag(v2f i) :SV_Target
            {
                ......
                //光照反射
                fixed3 reflectDir = normalize(reflect(-worldLightDir, i.worldNormal));//计算入射光关于法线的反射方向
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);//视角方向
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
                //这一步求漫反射和环境光和高光
                fixed3 color = ambient + diffuse + specular;
                return fixed4(color,1);
            }
            ENDCG
        }
    }
}

效果如下所示:

BlinnPhong

BlinnPhong光照模型和Phong模型的区别则是:Phong的高光计算是视角与“入射光相对法线反射之后的光”作点积运算,而BlinnPhong的高光计算则是法线与“视角和入射光相加后归一化”的向量作点积运算。

因此只需要修改部分Specular Pixel Level的片元着色器代码即可完成任务:

            fixed4 frag(v2f i) :SV_Target
            {
                ......

                //光照反射
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));//视角方向
                fixed3 halfDir = normalize(viewDir+worldLightDir);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(halfDir, i.worldNormal)), _Gloss);
                //这一步求漫反射和环境光和高光
                fixed3 color = ambient + diffuse + specular;
                return fixed4(color,1);
            }

 相比于Phong模型的高光反射部分看起来会更大更亮一些:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1635029.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【SpringBoot】数据脱敏

文章目录 什么是数据脱敏JsonSerialize自定义Jackson注解定制脱敏策略定制JSON序列化实现脱敏工具类 定义Person类,对其数据脱敏模拟接口测试总结 什么是数据脱敏 数据脱敏,也称为数据的去隐私化或数据变形,是一种技术手段,用于对…

tcp inflight 守恒算法的自动收敛

inflight 守恒算法看起来只描述理想情况,现实很难满足,是这样吗? 从 reno 到 bbr,无论哪个算法都在描述理想情况,以 reno 和 bbr 两个极端为例,它们分别描述两种理想管道,reno 将 buffer 从恰好…

【C++庖丁解牛】C++11---新的类的功能 | 可变参数模板

🍁你好,我是 RO-BERRY 📗 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 🎄感谢你的陪伴与支持 ,故事既有了开头,就要画上一个完美的句号,让我们一起加油 目录 1.新的类功能1.1 默认成员…

浏览器预加载器如何使页面加载速度更快

预加载器(也称为推测或前瞻预解析器)可能是浏览器性能有史以来最大的改进。 那么什么是预加载器以及它如何提高性能呢? 浏览器如何加载网页 网页充满了依赖关系——在下载相关的CSS之前页面无法开始渲染,然后当遇到脚本时HTML解…

零基础该如何自学linux运维?

零基础该如何自学linux运维?以下是建议帮助你入门Linux运维的一些建议。 一、自学建议: 理解基础概念:首先,你需要对Linux操作系统的基本概念有所了解,包括文件系统、用户权限、进程管理等。安装Linux系统&#xff1…

若依前后端部署系统--详细附图

一、后端部署 1、在ruoyi项目的Maven中的生命周期下双击package.bat打包Web工程,生成jar包文件。 提示打包成功 2、多模块版本会生成在ruoyi/ruoyi-admin模块下target文件夹,我们打开目录ruoyi-admin/taget,打开cmd,运行java -jar jar包名称…

【C语言进阶】程序编译中的预处理操作

📚作者简介:爱编程的小马,正在学习C/C,Linux及MySQL.. 📚以后会将数据结构收录为一个系列,敬请期待 ● 本期内容讲解C语言中程序预处理要做的事情 目录 1.1 预处理符号 1.2 #define 1.2.1 #define定义标识…

【Docker学习】docker stats

命令: docker container stats 描述: 显示容器资源使用的状态(实时) 用法: docker container stats [OPTIONS] [CONTAINER...] 别名: docker stats(docker的一些命令可以简写,docker stats就等同…

Odoo17开发环境搭建

1.先下载godoo17_20240227_02.zip压缩包,里面包含了项目用到的所有的插件了,直接使用这个包即可。 下载地址:https://download.csdn.net/download/java173842219/89242257 2.解压该压缩包,目录如下: 3.下载pycharm并…

如何保证Redis双写一致性?

目录 数据不一致问题 数据库和缓存不一致解决方案 1. 先更新缓存,再更新数据 该方案数据不一致的原因 2. 先更新数据库,再更新缓存 3. 先删除缓存,再更新数据库 延时双删 4. 先更新数据库,再删除缓存 该方案数据不一致的…

声明式事务(@Transactional)使用时需要注意的坑

前言 上两篇文章已经详细分析了申明式事务的实现原理,知道了底层原理之后,现在就可以开始使用申明式事务去简化我们的代码了。但是在使用Transactional注解的时候也会经常遇到一些问题,有些问题不仔细测试观察的话还不容易发现,比…

Windows vbs脚本定时给焦点窗口发送消息

直接上脚本代码,你们可以自己看着改 MsgInputbox("message1") Msg1Inputbox("message2") numInputbox("number")a1 bnumset wshshellCreateObject("wscript.shell") 创建Windows的shell对象打开shell窗口 wscript.sleep 5000for ia t…

vue本地调试devtools

一、谷歌浏览器加载扩展程序 二、把解压的压缩包添加即可,重启浏览器 三、启动前端本地项目,即可看到Vue小图标

Linux|awk 特殊模式“BEGIN 和 END”

引言 在本文[1],我们将介绍Awk的更多特性,特别是两个特殊的模式:BEGIN和END。 这些独特的功能在我们努力扩展和深入探索构建复杂Awk操作的多种方法时,将大有裨益。 实例 让我们从Awk系列的开篇回顾开始,回想一下&#…

SSL证书一般是怎么收费的?

SSL证书的费用通常按照以下几个因素决定: 1. 证书类型: - 域名验证(DV)证书:这是最基本的类型,仅验证域名所有权,费用一般在几十到几百之间. - 组织验证(OV)证书&#xf…

【触摸案例-手势解锁案例-九宫格 Objective-C语言】

一、手势解锁案例,九宫格,我们先来分析一下怎么实现: 首先呢,我们先来运行一下, 这一块儿,上面的这九个东西,肯定是要有一个九宫格的一个算法的问题,然后呢,上边的这九个小圆圈儿,这是什么东西,Button,为什么是Button,因为可以点,是吗,就因为这个?实际上,你用…

LeetCode55:跳跃游戏

题目描述 给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。 解题思想 每次…

软考-信息系统项目管理师-论文技术架构模板(60天备考第26天)

分享一段信息系统项目管理师论文项目技术架构描述的万能模板,供大家参考。距离考试还有二十八天,如果论文写不好的可以加微进论文指导群学习论文写作。 该系统前端基于Vue开发,后端基于java开发,前后端分离部署。整体采用B/S架构&…

【webrtc】MessageHandler 5: 基于线程的消息处理:以PeerConnection信令线程为例

peerconn的信令是通过post 消息到自己的信令线程消息来处理的PeerConnectionMessageHandler 是具体的处理器G:\CDN\rtcCli\m98\src\pc\peer_connection_message_handler.hMachinery for handling messages posted to oneself PeerConnectionMessageHandler 明确服务于 signalin…

公网ip申请ssl仅260

现在很多网站都已经绑定域名,因此使用的都是域名SSL证书保护网站传输信息安全,而没有绑定域名只有公网IP地址的网站想要保护传输信息安全就要申请IP SSL证书。IP SSL证书也是由正规CA认证机构颁发的数字证书,用来保护用户的隐私以及数据安全&…