พื้นผิวเคลื่อนไหวสำหรับรุ่น; วิธีการเขียน Shader หรือไม่?


9

กำลังดู Rocket League และสังเกตว่ามีสติ๊กเกอร์เคลื่อนไหวและล้อ

ภาพ.

ฉันต้องการใช้งานสิ่งที่คล้ายกับเอฟเฟกต์ในภาพด้านบน

ฉันจะเขียน Unity Shader เพื่อสร้างเอฟเฟกต์ล้อได้อย่างไร

ฉันไม่ค่อยรู้เรื่อง Shaders มากนัก แต่ฉันสามารถแก้ไข Unity Shader มาตรฐานเพื่อสร้างเอฟเฟกต์ภาพเคลื่อนไหวได้หรือไม่?


3
Shaders เป็นทางออกที่ต้องไปแน่นอนที่นี่แม้จะมีอะไรที่เรียบง่ายเหมือนกับพื้นผิวแบบเลื่อน Unity มี_Timeตัวแปรในตัวที่คุณสามารถเพิ่มลงในพิกัดพื้นผิวของคุณในส่วน (จุดสุดยอด) เพื่อให้ได้เอฟเฟกต์การเลื่อนที่มีราคาถูก เอฟเฟกต์รูปหกเหลี่ยมก็ค่อนข้างตรงไปตรงมาเช่นกัน หากคุณแก้ไขคำถามของคุณเพื่อเน้นเพียงเอฟเฟกต์เดียวและถามว่า "ฉันจะใช้สิ่งนี้ใน Unity shader ได้อย่างไร" เราอาจช่วยคุณได้ ตรวจสอบให้แน่ใจว่าได้ระบุว่า shader จำเป็นต้องตอบสนองต่อแสง / เงาเนื่องจากมีผลกระทบต่อวิธีการเขียน
DMGregory

ขอบคุณสำหรับข้อมูล. เปลี่ยนคำถามของฉันแล้วดีกว่าไหม ฉันไม่รู้จริงเกี่ยวกับแสงและเงาดังนั้นตอนนี้ฉันก็เลยออกไปจนกว่าฉันจะเข้าใจ Shaders ได้ดีขึ้น ฉันเดาว่าฉันต้องการพวกเขา แต่ฉันสามารถคัดลอกชิ้นส่วนจากตัวเปลี่ยนมาตรฐานใช่ไหม
JacketPotatoeFan

3
ฉันจะขยายคำตอบในภายหลังในคืนนี้เล็กน้อย (แต่อย่าลังเลที่จะเอาชนะฉันหากใครต้องการคำตอบตอนนี้!) - แต่คุณมักจะเขียน Unity shader ที่ใช้แสงเป็น "Surface Shader" ในขณะที่คนที่ไม่ต้องการแสงสว่างมักจะง่ายกว่าในการเขียนเป็น "Unlit Shader" สำหรับฉันดูเหมือนว่าล้อเหล่านั้นไม่ได้ส่องแสง (เงาเล็กน้อยที่ขอบดูเหมือน SSAO) ดังนั้นฉันจึงปล่อยให้แสงส่องจากภาพเพื่อเริ่ม คุณสามารถอธิบายได้ไหม: คุณต้องการรูปแบบทางเรขาคณิตที่แสดงที่นี่หรือความสามารถในการเลื่อนพื้นผิวโดยพลการออกไปข้างนอก
DMGregory

ขอบคุณมากข้อมูลอะไรก็ตามที่คุณมอบให้ฉันเพื่อเริ่มต้นใช้งานจะยอดเยี่ยมมาก ฉันชื่นชมคนที่สามารถเขียนเฉดสีได้ฉันได้ดูบทความสองสามเรื่องเกี่ยวกับ Unity Shaders และใช่ว่าสับสนมาก ฉันคิดว่าการเลื่อนผ่านพื้นผิว (หรือพื้นผิวยูวี) จะดีพอและมีความสามารถในการย้อมสี
JacketPotatoeFan

คำตอบ:


13

ฉันจะสร้างมันขึ้นมาในไม่กี่ชั้นเพื่อให้คุณสามารถดูว่ามันมารวมกัน

เริ่มต้นด้วยการสร้าง shader ใหม่ใน Unity โดยเลือกCreate --> Shader --> Unlitในเมนูสินทรัพย์หรือคลิกขวาที่เมนูบริบทในหน้าต่างโครงการ

ในบล็อกด้านบนเราจะเพิ่ม_ScrollSpeedsพารามิเตอร์เพื่อควบคุมความเร็วของการเคลื่อนไหวของพื้นผิวในแต่ละทิศทาง:

Properties
{
    _MainTex ("Texture", 2D) = "white" {}
    _ScrollSpeeds ("Scroll Speeds", vector) = (-5, -20, 0, 0)
}

สิ่งนี้จะเปิดเผยคุณสมบัติโฟลตแบบ 4 องค์ประกอบใหม่ในตัวตรวจสอบวัสดุที่มีชื่อ "Scroll Speeds" ที่เป็นมิตร (คล้ายกับการเพิ่มpublicหรือSerializedตัวแปรให้กับMonoBehaviourสคริปต์)

ต่อไปเราจะใช้ตัวแปรนี้ใน Vertex shader เพื่อเลื่อนพิกัดพื้นผิว ( o.uv) โดยเพิ่มเพียงสองบรรทัดใน shader เริ่มต้น:

sampler2D _MainTex;
float4 _MainTex_ST;
// Declare our new parameter here so it's visible to the CG shader
float4 _ScrollSpeeds;

v2f vert (appdata v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);

    // Shift the uvs over time.
    o.uv += _ScrollSpeeds * _Time.x;

    UNITY_TRANSFER_FOG(o,o.vertex);
    return o;
}

ตบที่รูปสี่เหลี่ยม (มีเนื้อยีราฟฟรีน่ารักโดย Kenney ) แล้วคุณจะได้รับ:

รูปสี่เหลี่ยมที่มีการเลื่อนยีราฟซ้ำ ๆ

ในการทำให้พื้นผิวเลื่อนออกไปด้านนอกในวงแหวนเราสามารถใช้การแบ่งตาข่ายเหมือนใยแมงมุมโดยมีค่า uv v เพิ่มขึ้นจากกึ่งกลาง แต่นั่นจะให้สิ่งประดิษฐ์ที่มีรูปทรงใบเลื่อยด้วยตัวเอง ฉันจะแสดงให้เห็นว่าเราสามารถคำนวณยูวีของเราได้อย่างไรต่อชิ้นส่วน

นี่เป็นค่าใช้จ่ายที่สูงกว่าเล็กน้อยทั้งเนื่องจากการดำเนินการตรีโกณมิติและความยาว (ซึ่งมีราคาแพงกว่าคณิตศาสตร์พื้นฐาน) และเนื่องจากมันไม่ได้มีประสิทธิภาพในการทำนาย & แคชข้อมูลพื้นผิวในฮาร์ดแวร์เมื่อคำนวณ texcoords ต่อชิ้นส่วนเมื่อเปรียบเทียบกับการแก้ไข ระหว่างจุดยอด แต่สำหรับเอฟเฟกต์พิเศษแบบนี้มันไม่มากเกินไป

v2f vert (appdata v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);

    // Shift the UVs so (0, 0) is in the middle of the quad.
    o.uv = v.uv - 0.5f;

    UNITY_TRANSFER_FOG(o,o.vertex);
    return o;
}

fixed4 frag (v2f i) : SV_Target
{
    // Convert our texture coordinates to polar form:
    float2 polar = float2(
           atan2(i.uv.y, i.uv.x)/(2.0f * 3.141592653589f), // angle
           length(i.uv)                                    // radius
        );

    // Apply texture scale
    polar *= _MainTex_ST.xy;

    // Scroll the texture over time.
    polar += _ScrollSpeeds.xy * _Time.x;

    // Sample using the polar coordinates, instead of the original uvs.
    // Here I multiply by MainTex
    fixed4 col = tex2D(_MainTex, polar);

    // apply fog
    UNITY_APPLY_FOG(i.fogCoord, col);
    return col;
}

นั่นทำให้เราเป็นแบบนี้ (ที่นี่ฉันเพิ่มพารามิเตอร์การเรียงในวัสดุเพื่อให้ชัดเจนยิ่งขึ้นว่าเกิดอะไรขึ้น - การตัดกระเบื้องซ้ำรอบ ๆ วงกลมดูบิดเบี้ยวและแปลก)

ยีราฟปูกระเบื้องกระจายแสงออกไปด้านนอกจากจุดศูนย์กลางของรูปสี่เหลี่ยม

สุดท้ายเพื่อย้อมสีพื้นผิวโดยการไล่ระดับสีเลื่อนเราสามารถเพิ่มการไล่ระดับสีเป็นพื้นผิวที่สองและคูณพวกเขาเข้าด้วยกัน

ก่อนอื่นเราเพิ่มพารามิเตอร์ texture ใหม่ที่ด้านบน:

Properties
{
    _MainTex ("Texture", 2D) = "white" {}
    _TintTex("Tint Texture", 2D) = "white" {}
    _ScrollSpeeds ("Scroll Speeds", vector) = (-5.0, -20.0, 0, 0)
}

และประกาศในบล็อก CGPROGRAM ของเราเพื่อให้ CG shader เห็นได้:

sampler2D _MainTex;
float4 _MainTex_ST;

// Declare our second texture sampler and its Scale/Translate values
sampler2D _TintTex;
float4 _TintTex_ST;

float4 _ScrollSpeeds;

จากนั้นอัปเดตส่วนย่อยของเราเพื่อใช้พื้นผิวทั้งสอง:

fixed4 frag(v2f i) : SV_Target
{
    float2 polar = float2(
           atan2(i.uv.y, i.uv.x) / (2.0f * 3.141592653589f), // angle
           length(i.uv)                                    // radius
        );

    // Copy the polar coordinates before we scale & shift them,
    // so we can scale & shift the tint texture independently.
    float2 tintUVs = polar * _TintTex_ST.xy;
    tintUVs += _ScrollSpeeds.zw * _Time.x;

    polar *= _MainTex_ST.xy;
    polar += _ScrollSpeeds.xy * _Time.x;

    fixed4 col = tex2D(_MainTex, polar);
    // Tint the colour by our second texture.
    col *= tex2D(_TintTex, tintUVs);

    UNITY_APPLY_FOG(i.fogCoord, col);
    return col;
}

และตอนนี้ยีราฟของเราได้รับ trippy จริงๆ:

ไกลออกไปผู้ชาย ....

ด้วยการเลือกพื้นผิวและอัตราการเลื่อนที่ละเอียดกว่าเล็กน้อยทำให้สามารถสร้างเอฟเฟกต์ที่คล้ายกับที่แสดงในคำถามได้


คุณอาจสังเกตเห็นสิ่งประดิษฐ์เล็ก ๆ สองชิ้นกับรุ่นที่ฉันแสดงด้านบน:

  • ใบหน้าที่อยู่ใกล้ศูนย์กลางของวงกลมจะได้รับการยืด / ผอม / แหลมจากนั้นเมื่อพวกเขาเคลื่อนที่ไปด้านนอกพวกเขาจะถูกแบน / กว้าง

    ความผิดเพี้ยนนี้เกิดขึ้นเพราะเรามีใบหน้าจำนวนหนึ่งรอบปริมณฑล แต่จำนวนเส้นรอบวงที่พวกมันขยายออกกว้างขึ้นเมื่อรัศมีเพิ่มขึ้นขณะที่ความสูงยังคงเท่าเดิม

    เราสามารถแก้ไขสิ่งนี้ได้โดยทำการแมปองค์ประกอบแนวตั้งของตัวอย่างพื้นผิวใหม่เพื่อติดตามโค้งลอการิทึมดังนั้นการทำซ้ำของพื้นผิวจะแยกออกไปอีกเมื่อรัศมีเพิ่มขึ้นและเข้าใกล้จุดศูนย์กลางมากขึ้น (อันที่จริงแล้วสิ่งนี้ทำให้เรามียีราฟน้อยลงและเล็กลง ... )

  • มีแถวหนึ่งหรือสองพิกเซลที่พร่ามัวไปทางซ้ายกลางของรูปสี่เหลี่ยม

    สิ่งนี้เกิดขึ้นเนื่องจาก GPU ดูที่พิกัดตัวอย่างเนื้อสัมผัสสองอันเพื่อหาว่าตัวกรองใดที่จะใช้ เมื่อกลุ่มตัวอย่างอยู่ใกล้กันมันจะแสดงพื้นผิวที่แสดงขนาดใหญ่ / ปิดและแสดงระดับ mip ที่มีรายละเอียดมากที่สุด เมื่อตัวอย่างอยู่ห่างไกลกันมันเดาว่าเราจะต้องแสดงพื้นผิวด้วยการซูมขนาดเล็กหรือห่างออกไปและมันจะสุ่มตัวอย่างจากแผนที่ขนาดเล็ก / พร่าเลือนเพื่อให้แน่ใจว่าเราจะไม่ได้รับวัตถุนามแฝงประกาย

    ปัญหาอยู่ตรงนี้เราอยู่ที่จุดพันรอบพิกัดพิกัดจาก -180 ถึง 180 องศา ดังนั้นเราจึงสุ่มตัวอย่างจากจุดที่คล้ายกันมากในพื้นที่ผิวซ้ำของเราถึงแม้ว่าพิกัดตัวเลขของพวกเขาทำให้พวกเขาดูเหมือนว่าพวกเขาห่างกัน ดังนั้นเราสามารถให้เวกเตอร์ไล่ระดับสีสุ่มตัวอย่างของเราเองเพื่อแก้ไขสำหรับสิ่งนี้

นี่คือรุ่นที่มีการแก้ไขเหล่านี้:

fixed4 frag(v2f i) : SV_Target
{
    float2 polar = float2(
           atan2(i.uv.y, i.uv.x) / (2.0f * 3.141592653589f), // angle
           log(dot(i.uv, i.uv)) * 0.5f                       // log-radius
        );

    // Check how much our texture sampling point changes between
    // neighbouring pixels to the sides (ddx) and above/below (ddy)
    float4 gradient = float4(ddx(polar), ddy(polar));

    // If our angle wraps around between adjacent samples,
    // discard one full rotation from its value and keep the fraction.
    gradient.xz = frac(gradient.xz + 1.5f) - 0.5f;

    // Copy the polar coordinates before we scale & shift them,
    // so we can scale & shift the tint texture independently.
    float2 tintUVs = polar * _TintTex_ST.xy;
    tintUVs += _ScrollSpeeds.zw * _Time.x;

    polar *= _MainTex_ST.xy;
    polar += _ScrollSpeeds.xy * _Time.x;

    // Sample with our custom gradients.
    fixed4 col = tex2Dgrad(_MainTex, polar, 
                           _MainTex_ST.xy * gradient.xy,
                           _MainTex_ST.xy * gradient.zw
                         );

    // Since our tint texture has its own scale,
    // its gradients also need to be scaled to match.
    col *= tex2Dgrad(_TintTex, tintUVs,
                          _TintTex_ST.xy * gradient.xy,
                          _TintTex_ST.xy * gradient.zw
                         );

    UNITY_APPLY_FOG(i.fogCoord, col);
    return col;
}

2
การปรับปรุงเล็ก ๆ น้อย ๆ ที่สามารถทำได้: 1) การใช้เส้นโค้งลอการิทึมสำหรับทิศทาง v ช่วยให้พื้นผิวคงรูปร่างเมื่อมันเลื่อนออกไปด้านนอก (แทนที่จะทำให้แคบลงถึงจุดกึ่งกลาง mip smears ออก) 2) การคำนวณการไล่ระดับพื้นผิวของคุณเองและการใช้tex2Dgradแก้ไขสิ่งประดิษฐ์การกรองที่มุมล้อมรอบจาก -pi ถึง + pi (นั่นคือเส้นบาง ๆ ของพิกเซลเบลอทางด้านซ้าย) นี่คือผลลัพธ์แบบสัมผัสที่มีสีน้ำเงินมากขึ้น
DMGregory

น่าทึ่งขอบคุณสำหรับการทำลายเช่นกัน ฉันรู้สึกว่า Shaders เป็นสิ่งที่ฉันไม่สามารถเขียนได้ (โดยเฉพาะอย่างยิ่งสิ่งที่คุณทำเพื่อ "ขั้วโลก" สิ่งคณิตศาสตร์ไม่ได้มีความหมายอะไรเลยสำหรับฉันเพราะฉันมีทักษะคณิตศาสตร์พื้นฐานมากเท่านั้น)
JacketPotatoeFan

1
สิ่งที่คนมักจะมองหา ;) ฉันทำมันมากพอที่มันจะม้วนแป้น ลองเล่นกับฟังก์ชั่นคณิตศาสตร์ที่แตกต่างกันและดูผลลัพธ์ - การมีเครื่องมือเช่นนี้เพื่อช่วยให้ฉันเห็นภาพว่าคณิตศาสตร์กำลังทำอะไรอยู่ในรูปทรงเรขาคณิตคือวิธีที่ฉันได้ฝึกฝนในตอนแรก (ไม่ใช่กับ shaders โดยเฉพาะ แต่เป็นเครื่องมือสร้างภาพ WinAmp แบบเก่าที่เรียกว่า WhiteCap ... ) แม้ในขณะที่คณิตศาสตร์เชดเดอร์นั้นผิดพลาดไปอย่างน่ากลัว : D
DMGregory
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.