基于Shader实现的3D模型绘图

基于Shader实现的3D模型绘图

  • 简述
  • 具体实现
    • 前提
    • 脚本实现
    • Shader实现
    • 项目地址
    • 后记

简述

网上大多实现3D模型绘制通过遍历所有像素进行笔刷范围内检测,对于大图和高模而言,效率太低,无法使用。
基于Shader的实现方案通过将模型在渲染表面上额外多绘制一次,将UV处理后直接当作位置,世界空间的位置作为额外内容,作为顶点着色色器输出。在像素着色器中进行笔刷范围检测。然后将该渲染表面作为模型材质贴图。
效果如下:
基于Shader实现的3D模型绘图_第1张图片

具体实现

目前作者在unity中实现。读者明白原理后很方便在其他引擎中实现。

前提

模型uv必须要无重复,如果uv重复的话会导致在一处绘制之后,另一处同样受到影响。基本所有的3D绘制都有这个要求。

脚本实现

首先,获取被绘制的模型材质中使用的贴图,通过该贴图创建相同的渲染表面,并将材质中的贴图引用改为该渲染表面。

        Renderer renderer = GetComponent<Renderer>();
        Material material = renderer.material;
        resourceTexture = material.GetTexture("_MainTex") as Texture2D;
        if(null == resourceTexture)
        {
            Debug.LogError("只支持修改Texture2D");
            return;
        }

        //创建渲染表面
        renderTexture = new RenderTexture(resourceTexture.width, resourceTexture.height, 0, RenderTextureFormat.ARGB32);
        material.SetTexture("_MainTex", renderTexture);
        Graphics.Blit(resourceTexture, renderTexture);

其次,使用Shader(custom/uvRender)创建绘制的材质,并在每一帧中添加渲染命令,将被绘制模型通过该材质渲染到渲染表面上

        //创建绘制材质
        if(null == paintMaterial)
        {
            paintMaterial = new Material(Shader.Find("Custom/UVRender"));
        }
        
        //加入绘制指令
        renderCommand = new CommandBuffer();
        renderCommand.SetRenderTarget(renderTexture);
        renderCommand.DrawRenderer(renderer, paintMaterial, 0, -1);
        Camera.main.AddCommandBuffer(CameraEvent.AfterEverything, renderCommand);

然后在每一帧更新笔刷的位置信息即可。为了方便,作者这边直接使用球体作为笔刷的位置范围。

    void Update()
    {
        Vector3 position = paintCircle.position;
        float scale = paintCircle.localScale.x / 2;
        paintMaterial.SetVector("_CirclePoint", new Vector4(position.x, position.y, position.z, scale));
    }

这样脚本部分的工作就完成了。

Shader实现

1.直接创建unity的unLight Shader
2.Shader中添加命令,关闭三角形背面剔除,我们要保证所有面片都被绘制,某些模型面片在用uv作为位置时法线朝里会3.导致被剔除。
3.Shader中将混合模式改为SrcAlphaOneMinusSrcAlpha,模拟画刷的层层叠加,越来越深的效果。
4.在顶点着色器中将uv进行处理作为位置输出,z,w都强制设为1避免被深度剔除(读者也可通过其他方式保证)。这里的处理是将uv转换为透视空间,如果不转换的话,绘制出来的模型只会占据渲染表面的右下角。Y轴方向也会是反的。将世界坐标存储,用于像素着色器中进行范围检测。


            v2f vert (appdata v)
            {
                v2f o;
                o.worldPosition =  mul(unity_ObjectToWorld, v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.vertex = float4(v.uv * 2 - 1, 1.0, 1.0);
                o.vertex.y = -o.vertex.y;
                //o.vertex = float4(v.uv, 1.0, 1.0);
                return o;
            }

5.在像素着色器中,对世界坐标与球心位置计算距离,判断像素是否在画刷范围内。如果在画刷范围内直接返回一定的颜色值(读者也可通过距离作为uv对贴图进行采样返回特别的样式),不在的话则不绘制。

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                float3 dir = _CirclePoint.xyz - i.worldPosition.xyz;
                float distance = length(dir);
                if(distance < _CirclePoint.w)
                {
                    float radio = distance / _CirclePoint.w * 0.5;
                    radio = 1 - radio * radio;

                    return float4(0, 0, 1, radio / 10);
                }
                clip(-1);
                return float4(0, 0, 0, 0);
            }

渲染表面的绘制效果如图
基于Shader实现的3D模型绘图_第2张图片

项目地址

项目地址

后记

从事或者对图形学有兴趣的可以加个关注,有助交流。后面我也会分享一些相关的技术方案

你可能感兴趣的:(Unity)