Shader 海面/水面

news2024/10/7 18:25:27

在这里插入图片描述
首先用Terrain在场景中随便做个地形,当作海底

在这里插入图片描述
上面加个Plane作为海面

实现海水效果要考虑海水深度对颜色的影响,法线移动形成波浪,菲涅尔,高光等效果

深度

海水深的地方颜色深,浅的地方颜色浅,所以海边和礁石附近的颜色应该比较浅。在shader中申明_CameraDepthTexture即可获得相机看到的深度图

在这里插入图片描述

因为海面会用Transparent来渲染,不会写入深度,所以这张深度图就是相机到海底的距离,在相机空间下,用海底的深度(红色箭头)减去海面的深度(蓝色箭头)就得到海面到海底的深度,关键代码

// 非线性深度
float depth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, i.scrPos).r;

// 转为线性深度,这个深度是相机看到的深度,也就是海底不透明物体深度,海面透明不会写入深度
depth = LinearEyeDepth(depth);

// 海面的深度
float seaSurfaceDepth = LinearEyeDepth(i.pos.z);

// 海面距离海底的深度
float biasDepth = depth - seaSurfaceDepth;

在这里插入图片描述
根据这个深度就可以在了两个颜色之间做插值

法线移动,菲涅尔

根据时间和X,Y两个方向的速度计算法线纹理的偏移量,关键代码

float2 speed = _Time.y * float2(_WaveXSpeed, _WaveYSpeed);
// 对法线纹理进行两次采样(这是为了模拟两层交叉的水面波动的效果)
float3 bumpOffset1 = UnpackNormal(tex2D(_WaterNormalMap, i.uv.zw + speed)).rgb;
float3 bumpOffset2 = UnpackNormal(tex2D(_WaterNormalMap, i.uv.zw - speed)).rgb;
// 两次结果相加并归一化后得到切线空间下的法线方向
float3 tangentNormal = normalize(bumpOffset1 + bumpOffset2);

把法线从切线空间变换到世间空间,就可以计算反射和折射,然后用菲涅尔系数混合,这部分代码和玻璃效果的实现类似,参考 玻璃效果

在这里插入图片描述
添加法线移动和菲涅尔后的效果

高光

使用Blinn-Phong计算高光

float3 halfDir = normalize(worldLightDir + worldViewDir);
float hdotn = max(0, dot(halfDir, worldNormal));
float3 specular = pow(hdotn, _SpecularPower) * _SpecularIntensity;
specular *= _LightColor0.rgb * _SpecularColor;

此外,可以在场景中添加一个反射探针ReflectionProbe,Bake下环境信息

在这里插入图片描述

添加高光后的效果

完整的shader

Shader "MyCustom/Sea"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        
        _ShallowColor		("[浅水区颜色]ShallowColor",		  Color)			 = (1, 1, 1, 1)
        _DeepColor			("[深水区颜色]DeepColor",		  Color)			 = (1, 1, 1, 1)
        _DepthRange			("[调节深度范围]DepthRange",		  Range(0,1))		 = 0.5
        _Cubemap			("Environment Cubemap",			  Cube)				 = "_Skybox" {}
        
        // 一个由噪声纹理生成的法线纹理
        _WaterNormalMap 	("WaterNormalMap",				  2D)				 = "bump" {}
        _WaveXSpeed			("Wave Horizontal Speed",		  Range(-0.1, 0.1))  = 0.01    
        _WaveYSpeed			("Wave Vertical Speed",			  Range(-0.1, 0.1))  = 0.01
        _Distortion			("[折射时图像的扭曲程度]Distortion", Range(0, 100))	 = 10
    	_NormalScale		("NormalScale",					  Float)			 = 4
    	
    	_SpecularColor		("SpecularColor",				  Color)			 = (1, 1, 1, 1)
    	_SpecularPower      ("SpecularPower",     			  Range(0, 150)) 	 = 100
        _SpecularIntensity  ("SpecularIntensity", 			  Range(0, 10))  	 = 0.6
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent"}
        ZWrite Off
        
        GrabPass
        {
            "_GrabTexture"
        }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            struct appdata
            {
                float4 vertex  : POSITION;
                float3 normal  : NORMAL;
				float4 tangent : TANGENT; 
                float2 uv      : TEXCOORD0;
            };

            struct v2f
            {
                float4 pos      : SV_POSITION;
                float4 uv       : TEXCOORD0;
                float4 scrPos   : TEXCOORD1;
                float4 TtoW0    : TEXCOORD2;  
				float4 TtoW1    : TEXCOORD3;  
				float4 TtoW2    : TEXCOORD4;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            sampler2D _GrabTexture;
            float4 _GrabTexture_TexelSize;
            
            // sampler2D_float 精度更高
            sampler2D_float _CameraDepthTexture;
            float3 _ShallowColor;
            float3 _DeepColor;
            float _DepthRange;
            samplerCUBE _Cubemap;
            
            sampler2D _WaterNormalMap;
            float4 _WaterNormalMap_ST;
            float _WaveXSpeed;
            float _WaveYSpeed;
            float _Distortion;
            float _NormalScale;

            float3 _SpecularColor;
            float _SpecularPower;
            float _SpecularIntensity;

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
                o.uv.zw = TRANSFORM_TEX(v.uv, _WaterNormalMap);
                o.scrPos = ComputeScreenPos(o.pos);

                float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;  
				float3 worldNormal = UnityObjectToWorldNormal(v.normal);  
				float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);  
				float3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;

                // 切线空间到世界空间的变换矩阵
                o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);  
				o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);  
				o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
                
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
				fixed4 albedo = tex2D(_MainTex, i.uv.xy);
            	float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
				float3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));
            	float3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
            	
                // 非线性深度
                float depth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, i.scrPos).r;
                // 等价于下面写法
                // float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.scrPos.xy / i.scrPos.w).r;

                // 转为线性深度,这个深度是相机看到的深度,也就是海底不透明物体深度,海面透明不会写入深度
                depth = LinearEyeDepth(depth);
                
                // 海面的深度
                float seaSurfaceDepth = LinearEyeDepth(i.pos.z);

                // 海面距离海底的深度
                float biasDepth = depth - seaSurfaceDepth;
                // 调节深度范围,指数部分为负数,所以是一条倾斜向下的曲线,深度越大越接近0
                biasDepth = exp(-_DepthRange * biasDepth);
            	fixed3 baseColor = lerp(_DeepColor, _ShallowColor, biasDepth);

                float2 speed = _Time.y * float2(_WaveXSpeed, _WaveYSpeed);
                // // 对法线纹理进行两次采样(这是为了模拟两层交叉的水面波动的效果)
                float3 bumpOffset1 = UnpackNormal(tex2D(_WaterNormalMap, i.uv.zw * _NormalScale + speed)).rgb;
                float3 bumpOffset2 = UnpackNormal(tex2D(_WaterNormalMap, i.uv.zw * _NormalScale - speed)).rgb;
            	// 两次结果相加并归一化后得到切线空间下的法线方向
            	float3 tangentNormal = normalize(bumpOffset1 + bumpOffset2);
                
                // 将法线转换为世界空间
				float3 worldNormal = normalize(half3(dot(i.TtoW0.xyz, tangentNormal), dot(i.TtoW1.xyz, tangentNormal), dot(i.TtoW2.xyz, tangentNormal)));

            	// 反射
				fixed3 reflectDir = reflect(-worldViewDir, worldNormal);
				fixed3 reflectCol = texCUBE(_Cubemap, reflectDir).rgb * albedo.rgb;

            	// 对切线空间下的法线进行偏移
                float2 offset = tangentNormal.xy * _Distortion * _GrabTexture_TexelSize.xy;
				// 把偏移量和屏幕坐标的z分量相乘,这是为了模拟深度越大、折射程度越大的效果
				i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;
				// 对scrPos进行了透视除法,使用该坐标对抓取的屏幕图像进行采样,得到模拟的折射颜色
            	fixed3 refractCol = tex2D(_GrabTexture, i.scrPos.xy / i.scrPos.w).rgb;

				// 计算菲涅耳系数,混合折射和反射
				fixed fresnel = pow(1 - saturate(dot(worldViewDir, worldNormal)), 4);
				fixed3 fresnelColor = reflectCol * fresnel + refractCol * (1 - fresnel);

            	// 使用Blinn-Phong计算高光
            	float3 halfDir = normalize(worldLightDir + worldViewDir);
				float hdotn = max(0, dot(halfDir, worldNormal));
				float3 specular = pow(hdotn, _SpecularPower) * _SpecularIntensity;
				specular *= _LightColor0.rgb * _SpecularColor;
            	
            	fixed4 finalCol = 1;
                finalCol.rgb = baseColor + fresnelColor + specular;
                return finalCol;
            }
            ENDCG
        }
    }
}

在这里插入图片描述
调节的参数

参考

《Shader 入门精要》

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

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

相关文章

fastDFS文件管理系统在linux下部署

1.概述 fastDFS分布式文件系统包括三个中要部分:追踪器、存储节点、客户端,可以使用文件存储,文件同步,文件访问等功能,用来存储大容量数据 存储节点集群: 横向扩容:增加存储容量 纵向扩容&…

liunx系统(VMware Workstation Pro)详细安装配置docker

​ 安装东西前要知道docker是什么,以及docker能都干什么,文章都是本人亲测然后写的过程. http://t.csdn.cn/iqbGg 博客文章链接详细介绍docker,以及部署MySQL,nginx等配置 一. liunx系统(VMware) 安装Docker 1. Docker中文网地址: Docker中文网 官网 (p2hp.com) 2. 打开VM…

50 Projects 50 Days - Progress Steps 学习记录

50 Projects 50 Days不使用任何前端框架,适合初学者练手,巩固前端基础,在这里记录一下学习过程,尤其是一些细节上的问题。 项目地址 Progress Steps 展示效果 Progress Steps 实现思路 进度条和结点分开处理: 1…

深入理解计算机系统第九章知识点总结

第九章 一些术语 PA(physical address):物理地址VA(virtual address):虚拟地址MMU(memory management unit):内存管理单元VP(virtual page):虚拟页PP(physical page):物理页/页帧SRAM:表示位于CPU和主存之…

详解Spring事务

目录 1.声明式事务 1.1.概述 1.2.使用 1.2.1.建表 1.2.2.maven依赖 1.2.3.配置 1.2.4.业务 1.2.5.测试 2.事务的传播行为 1.声明式事务 1.1.概述 spring中事务分为两种: 1.编程式事务,通过写代码来实现,每一步。 2.声明式事务&am…

华为手表开发:WATCH 3 Pro(15)传感器订阅加速度计

华为手表开发:WATCH 3 Pro(15)传感器订阅加速度计初环境与设备加速度传感器介绍与说明鸿蒙开发文件夹:文件重点新增展示的文本标记index.hmlindex.cssindex.js初 希望能写一些简单的教程和案例分享给需要的人 鸿蒙可穿戴开发 环…

Elasticsearch:索引状态是红色还是黄色?为什么?

在我之前文章 “Elasticsearch:如何调试集群状态 - 定位错误信息” 中,我有详细介绍如何调试集群状态。在今天的文章中,我将详细介绍如何故障排除和修复索引状态。 Elasticsearch 是一个伟大而强大的系统,特别是创建一个可扩展性极…

C++、STL标准模板库和泛型编程 ——关联式容器 (侯捷)

C、STL标准模板库和泛型编程——关联式容器 (侯捷)( 持续更新!!!) 关联式容器rb_tree 容器set、multiset 容器map、multimap容器C、STL标准模板库和泛型编程——序列式容器 (侯捷&am…

go+vue——go入门

govue技术选择入坑理由需要搭建前后端,Java 0 基础 ,环境容易出现问题;GO上手快,问题少推荐:【七米】代码博客搭建Go语言开发环境下载 并 安装检查是否安装好?GOPROXY 非常重要(帮你下载国外、G…

分布式锁Redision

目录 1.ab工具(压测工具)的安装 2.前置 3.优化 3.1synchronized修饰代码方法/代码块 3.2分布式锁事务的解决方案 3.3Redis实现锁问题 3.3.1 set ex方式 3.3.2 set ex方式设置过期时间 3.3.3单redis结点的解决UUID和LUA脚本 3.3.4redission解决分布式锁 4.Redission解…

数据结构:顺序表

朋友们、伙计们,我们又见面了,本期来给大家解读一下数据结构方面有关顺序表的相关知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成! C语言专栏:C语言:从入门…

阿里的Leader为什么牛逼?秘密都在“三板斧”里...

许多人都觉得,啊里出来的Leader,做事情都很有方法、有套路、有结果,秘密究竟在哪里?其实一个人的牛逼,首先是方法论的牛逼。本文就来聊聊,阿里Leader们都要学习的管理方法论,俗称阿里“三板斧”…

《MySQL系列-InnoDB引擎37》索引与算法-全文检索

全文检索 1 概述 对于B树的特点,可以通过索引字段的前缀进行查找。例如如下的查询方式是支持B树索引的,只要name字段添加了B树索引,就可以利用索引快速查找以XXX开头的名称。 select * from table where name like XXX%; 而如下这种情况不适合私有B索…

BUUCTF-sql注入联合查询的创建虚拟表-词频-steghide的使用

第七周第三次 目录 WEB [GXYCTF2019]BabySQli [GXYCTF2019]BabyUpload Crypto 世上无难事 old-fashion ​Misc 面具下的flag 九连环 WEB [GXYCTF2019]BabySQli 这是一道很新的题目 我们打开环境 发现登入注册界面 先看看源码有没有提示 发现有一个 php文件 进入…

Spark 对hadoopnamenode-log文件进行数据清洗并存入mysql数据库

一.查找需要清洗的文件 1.1查看hadoopnamenode-log文件位置 1.2 开启Hadoop集群和Hive元数据、Hive远程连接 具体如何开启可以看我之前的文章:(10条消息) SparkSQL-liunx系统Spark连接Hive_难以言喻wyy的博客-CSDN博客 1.3 将这个文件传入到hdfs中: hd…

OpenAI Translator | 基于ChatGPT API全局翻译润色解析及ORC上传图像翻译插件

简介 OpenAI Translator,一款基于 ChatGPT API 的划词翻译的浏览器插件和跨平台桌面端应用,使用 ChatGPT API 进行划词翻译和文本润色,借助了 ChatGPT 强大的翻译能力,帮助用户更流畅地阅读外语和编辑外语,允许跨 55 …

Qt音视频开发35-左右通道音量计算和音量不同范围值的转换

一、前言 视频文件一般会有两个声音通道及左右声道,值有时候一样有时候不一样,很多场景下我们需要对其分开计算不同的音量值,在QAudioFormat中可以获取具体有几个通道,如果是一个通道,则左右通道值设定一样&#xff0…

【时序数据库】时间序列数据和MongoDB第三部分-查询、分析和呈现时间序列数据...

在《时间序列数据和MongoDB:第1部分-简介》「时序数据库」时间序列数据与MongoDB:第一部分-简介中,我们回顾了理解数据库的查询访问模式需要询问的关键问题。在《时间序列数据和MongoDB:第2部分-模式设计最佳实践》「时序数据库」时序数据库和MongoDB第二…

Java实验课的学习笔记(二)类的简单使用

本文章就讲的是很基础的类的使用 重点大概就是类的构造函数以及一些很基础的东西。 实验内容是些老生常谈的东西,Complex类,在当初学C面向对象的时候也是这个样子展开的。 内容如以下: public class Complex {float real;float imag;public…

APK瘦身

先看下APK打包流程:APK打包流程_贺兰猪的博客-CSDN博客 知道了APK打包流程后想要瘦身,其实无非就是把整个APK的一些文件进行一个瘦身。 看下apk的这个文件。包括class、资源,资源生成arsc(资源映射表),manifest清单,…