ben davis
ben davis

Reputation: 1

Modifying Shader Properties from C#

I am trying to modify a custom shader's properties form C#. I'm able to see the properties updating in the inspector, but I don't see the changes reflected in the game unless I manually enter modify the values in the inspector.

Here is the relevant shader code:

Shader "UCLA Game Lab/Wireframe/Single-Sided"
{
    Properties
    {
        _Color("Line Color", Color) = (1,1,1,1)
        _MainTex("Main Texture", 2D) = "white" {}
        _Thickness("Thickness", Float) = 1
        _ValleyDepth("Depth", Range(0, 2)) = 0
        _HeightMap("Heightmap", 2D) = "black" {}
        _Scale("Scale", Range(0, 5)) = 1
        _Mask("Mask", 2D) = "black" {}
        _MaskWidth("Mask Width", Range(0, 120)) = 5
        _MaskHeight("Mask Height", Range(0, 120)) = 10

        [PerRendererData]_TravelX("Travel X", Float) = 0
        _TravelY("Travel Y", Float) = 0

    }

        SubShader
        {
            Pass
            {
                //Tags { "RenderType" = "Transparent" "Queue" = "Transparent" }

                Blend SrcAlpha OneMinusSrcAlpha
                //ZWrite Off
                LOD 200

                CGPROGRAM
                

                #pragma target 5.0
                #include "UnityCG.cginc"
                #include "UCLA GameLab Wireframe Functions.cginc"
                #pragma vertex vert
                #pragma fragment frag
                #pragma geometry geom


                float _ValleyDepth;
                sampler2D _HeightMap;
                float _Scale;
                float3 _WorldPOS;
                float _TravelX;
                float _TravelY;



                float random(float2 uv)
                {
                    return (_ValleyDepth * frac(sin(dot(uv,float2(12.9898,78.233)))*43758.5453123)  );
                }

        // Vertex Shader
        UCLAGL_v2g vert(appdata_base v)
        {

            _WorldPOS = mul(unity_ObjectToWorld, v.vertex); // ben

            //v.vertex.xyz += v.normal * random(v.texcoord);
            v.texcoord *= _Scale;
            v.texcoord.x += _TravelX;
            v.texcoord.y += _TravelY;
            fixed height = tex2Dlod(_HeightMap, float4(v.texcoord.xy, 0, 0)).r;
            v.vertex.xyz += v.normal * height * _ValleyDepth;
            return UCLAGL_vert(v);
        }

        // Geometry Shader
        [maxvertexcount(3)]
        void geom(triangle UCLAGL_v2g p[3], inout TriangleStream<UCLAGL_g2f> triStream)
        {
            UCLAGL_geom(p, triStream);
        }

        // Fragment Shader
        float4 frag(UCLAGL_g2f input) : COLOR
        {
            return UCLAGL_frag(input);
        }

    ENDCG
}
        }
}


And here is the c#:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MoveController : MonoBehaviour
{
    public Material heightMapX;

    

    public float speedX = 0f;
    public float speedY = 0f;



    Renderer thisRend; //Renderer of our Cube


    // Start is called before the first frame update
    void Start()
    {

        thisRend = GetComponent<Renderer>(); // grab the renderer component on our cube



        //heightMapX.EnableKeyword("_travelX"); 
        heightMapX.SetFloat("_TravelX", speedX);
        heightMapX.SetFloat("_TravelY", speedY);
    }

    // Update is called once per frame
    void Update()
    {

        if (Input.GetKeyDown(KeyCode.UpArrow))
        {
            speedX += 5f;
            //heightMapX.SetFloat("_TravelX", speedX);
            thisRend.material.SetFloat("_TravelX", speedX);
        }
        if (Input.GetKeyDown(KeyCode.RightArrow))
        {
            speedY -= 5f;
            //heightMapX.SetFloat("_TravelY", speedY);
            thisRend.material.SetFloat("_TravelY", speedY);
        }

    }
}

I tried using the [PerRendererData] directive on the shader variables but it didn't help.

Upvotes: 0

Views: 1560

Answers (2)

As Poultryghast mentioned, I'd recommend getting into the habit of using MaterialPropertyBlocks, as the performance gains are quite notable.

Here's another link to a more condensed and high-level view article explaining MPBs. https://thomasmountainborn.com/2016/05/25/materialpropertyblocks/

Essentially, what you're looking for is more something along the lines of :

private MaterialPropertyBlock myBlock;
private Renderer renderer;
    
public float speedX = 0f;
public float speedY = 0f;
    
void Awake()
{
    myBlock = new MaterialPropertyBlock(); // create a new PropertyBlock
    renderer = GetComponent<Renderer>(); // get your current renderer
}
    
void Start()
{
    renderer.GetPropertyBlock(myBlock); // retrieve the renderer's existing Property Block 
                                        // and store it into "myBlock"
    
    myBlock.SetFloat("_TravelX", speedX); // make your value changes in your new PB
    myBlock.SetFloat("_TravelY", speedY);
    
    renderer.SetPropertyBlock(myBlock); // apply your values onto the renderer's existing Block
}

From here, you can simply apply this same logic in your Update() method.

I'd recommend changing the values inside your if (Input.GetKeyDown(KeyCode)) statements, and calling the renderer.SetPropertyBlock(myBlock) at the end of the Update loop.

Either way, I believe this is a lot more optimized than going through the material everytime, but this has already been pointed out, and the article discusses it at greater lengths ;)

Upvotes: 2

Poultryghast
Poultryghast

Reputation: 58

You never actually set the material heightMapX back to the renderer. Try thisRend.material = heightMapX to actually apply the values of the material to the shader. Also, every time you do this it actually creates a new material which affects performance if you are doing this every frame. It's a little bit advanced but you can solve this problem using MaterialPropertyBlocks and GPU Instancing. This is a good tutorial if you are interested.

Upvotes: 0

Related Questions