[unity/shaderlab]使用世界坐标对贴图采样/溶解世界代码学习01

前言

      这段时间在玩一款叫第七史诗的游戏,里面有一种怪物身上部分材质不是通过正常的模型UV值进行采样的,怪物魔性的动作和不正常的纹理采样让这个怪物看起来非常的喜感有趣。

第七史诗中运用非模型UV对纹理采样

      刚好最近有读到关于溶解的代码,里面也有实现这种效果的功能,本来想自己实现一下,但是还是遇到了挺多问题的。这篇文章主要也是说一下自己遇到的一些问题,也好以后回顾学习。

先贴上溶解教程的源连接


1.让我们先实现一个可以贴图片的shader把!

      首先,我们先新建一个船新的shader文件,删除那些多余的东西,只保存一个图片变量,并把图片贴在模型的uv上,代码如下:

Shader "Custom/StandardTexture" {
    Properties {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200

        Pass {
            CGPROGRAM
            #include "Lighting.cginc"
            #pragma vertex vert
            #pragma fragment frag

            sampler2D _MainTex;
            float4 _MainTex_ST;

            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
            };

            struct v2f {
                float4 pos : SV_POSITION;
                float2 uvMainTex : TEXCOORD0;
            };

            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uvMainTex = TRANSFORM_TEX(v.texcoord, _MainTex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                fixed3 albedo = tex2D(_MainTex, i.uvMainTex).rgb;
                return fixed4(albedo, 1);
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
}

      上面的代码功能很简单,就是把图片贴在模型上。我们可以得到一个这样的cube

一个普通的cube

2.使用世界坐标代替模型UV对图片进行采样

      因为采样过程是在片元着色器进行的,我们要把顶点的世界坐标传给片元着色器。首先,在顶点输出结构体里添加用来存储世界坐标的字段。

      struct v2f {
          float4 pos : SV_POSITION;
          float2 uvMainTex : TEXCOORD0;
          float3 worldPos : TEXCOORD1;
      };

      然后还要在顶点着色器中把计算好的世界坐标传给片元着色器。

      v2f vert(a2v v) {
          v2f o;
          o.pos = UnityObjectToClipPos(v.vertex);
          o.uvMainTex = TRANSFORM_TEX(v.texcoord, _MainTex);
          o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
          return o;
      }

      最后在片元着色器中使用世界坐标代替之前的模型uv值。

      fixed4 frag(v2f i) : SV_Target {
          fixed3 albedo = tex2D(_MainTex, i.worldPos).rgb;
          return fixed4(albedo, 1);
      }
使用世界坐标采样图片的cube

      我们可以看到,只有前后两面是正确对图片采样的(其实并不能看到后面,但是后面确实是正确的),cube的顶面和左侧的面拿到的是错误的信息,为什么会这样呢?
      这是因为我们在使用tex2D函数的时候,第二个参数其实需要的是一个二维的参数,但是我们传入了一个三维的世界坐标。我们没有指明使用世界坐标的哪些分量对图片进行采样,unity默认就会使用世界坐标的xy坐标进行采样。
      cube的顶面y值都是相同的,所以要使用世界坐标的xz分量对图片采样我们才能拿到正确的效果。相对的,左右两面需要使用世界坐标的yz分量进行采样。

3.让cube的各个面使用不同的世界坐标进行采样

      关于让不同面使用不同坐标进行采样,还是通过看了网上的做法才有了思路,地址在这里
      文章中使用了顶点的法线信息与固定轴向点积,得到法线在轴上的投影来决定采样的坐标。所以我们也通过把法线分解(点积)为三个坐标轴方向的分量,再通过三个分量的大小对世界坐标进行分解与组合。我们在顶点着色器中进行计算,这样可以减少计算量。

      v2f vert(a2v v) {
          v2f o;
          o.pos = UnityObjectToClipPos(v.vertex);
          o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

          float NX = dot(v.normal, float3(1.0, 0.0, 0.0));
          float NY = dot(v.normal, float3(0.0, 1.0, 0.0));
          float NZ = dot(v.normal, float3(0.0, 0.0, 1.0));

          o.uvMainTex = o.worldPos.yz * NX + o.worldPos.zx * NY + o.worldPos.xy * NZ;

          return o;
      }

      还要把片元着色器中原来对世界坐标采样的代码修改为使用我们组合过的新UV值进行采样。

      fixed4 frag(v2f i) : SV_Target {
          fixed3 albedo = tex2D(_MainTex, i.uvMainTex).rgb;
          return fixed4(albedo, 1);
      }
使用新的UV对图片进行采样

      这个cube看起来以切正常,各个面在移动的时候都会根据世界坐标不同对纹理进行采样。
      但是,当这个cube旋转起来的时候,有的面就会出现采样不正常的情况。


旋转的cube

4.处理旋转问题

      为了查找上面的问题,我们把计算后的法线直接作为颜色输出到cube上,检查一下我们计算的过程是否有问题。首先,我们把上面NX,NY,NZ当做颜色的RGB值进行输出:


旋转法线

      我们发现,物体的法线并没有根据物体的旋转而旋转,我个人觉得这应该是因为旋转了模型,对应的旋转了模型的顶点,而模型顶点的法线信息是存储在顶点内的,所以旋转顶点并不会对模型法线造成任何影响。所以我尝试着把模型法线转到了世界坐标下:

          float3 worldNormal = mul(unity_ObjectToWorld, v.normal);
          float NX = abs(dot(worldNormal, float3(1.0, 0.0, 0.0)));
          float NY = abs(dot(worldNormal, float3(0.0, 1.0, 0.0)));
          float NZ = abs(dot(worldNormal, float3(0.0, 0.0, 1.0)));

      更改后的效果如下:


世界坐标下旋转法线

      我们把全新的法线应用到之前的代码中,可以发现我们已经一定程度上解决了之前的问题。注意上面使用了abs函数,是因为点积后的结果是分正负的,如果不取绝对值会导致cube的三个面的值是负的,输出到cube上的结果是黑色的。如果我们使用了取绝对值的分量,在角度达到某个值的时候旋转会反转:


取绝对值后的旋转

      取消绝对值后的旋转:


正常的旋转

      最后附上完整的shader代码:

Shader "Custom/StandardTexture" {
    Properties {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200

        Pass {  
            CGPROGRAM
            #include "Lighting.cginc"
            #pragma vertex vert
            #pragma fragment frag
            
            sampler2D _MainTex;
            float4 _MainTex_ST;
            
            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
            };
            
            struct v2f {
                float4 pos : SV_POSITION;
                float2 uvMainTex : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
            };
            
            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                float3 worldNormal = mul(unity_ObjectToWorld, v.normal);

                float NX = dot(worldNormal, float3(1.0, 0.0, 0.0));
                float NY = dot(worldNormal, float3(0.0, 1.0, 0.0));
                float NZ = dot(worldNormal, float3(0.0, 0.0, 1.0));

                o.uvMainTex = o.worldPos.yz * NX + o.worldPos.zx * NY + o.worldPos.xy * NZ;

                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target {
                fixed3 albedo = tex2D(_MainTex, i.uvMainTex).rgb;
                return fixed4(albedo, 1);
            }
            
            ENDCG
        }
    }
    FallBack "Diffuse"
}

番外:基于屏幕坐标采样

      得到了热心网友的反馈说,Epic7中可能不是通过世界坐标采样的,而是通过屏幕坐标进行采样的。好像确实是这样,那我们就在这里补充一下如何对屏幕坐标进行采样。我们可以使用unity提供的获取屏幕坐标的函数ComputeScreenPos(),然后再对得到的屏幕坐标进行一下齐次运算就可以得到视口空间下的坐标。最后使用视口坐标对图片采样就可以啦。

使用屏幕坐标进行采样

使用屏幕坐标进行采样

效果就是这样啦,最后是代码:

            v2f vert(a2v v) {
                v2f o;
                UNITY_INITIALIZE_OUTPUT(v2f, o);
                o.pos = UnityObjectToClipPos(v.vertex);
                o.scrPos = ComputeScreenPos(o.pos);
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target {
                float2 wcoord = i.scrPos.xy/i.scrPos.w;
                fixed3 albedo = tex2D(_MainTex, wcoord).rgb;
                return fixed4(albedo, 1);
            }

结语

      虽然我们在一定程度上解决了第三个问题,但是在cube旋转到一定程度上还是会有几个面采样结果很奇怪的问题,在单一轴向旋转时都会有一些问题。
      由于我也是刚刚开始学习shader,个人水平有限,暂时还没想到好的解决办法,如果有好的解决方案也希望大神不吝赐教。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容