Custom shaders

Note

If you haven’t already done so, make sure you’ve completed the steps in Basic before starting this tutorial. This tutorial will be based on SAPIEN assets and rendering.

Setup

For this tutorial, we will start from the following code. Do remember replacing the token with your access token.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import sapien.core as sapien
from sapien.core import Pose
import numpy as np
from PIL import Image, ImageColor
import open3d
from sapien.asset import download_partnet_mobility

my_token = "Your Access Token"
sapien_assets_id = 179
urdf = download_partnet_mobility(sapien_assets_id, token=my_token)

sim = sapien.Engine()
renderer = sapien.VulkanRenderer(False)
sim.set_renderer(renderer)
scene = sim.create_scene()
scene.set_timestep(1 / 60)

scene.set_ambient_light([0.5, 0.5, 0.5])
scene.set_shadow_light([0, 1, -1], [0.5, 0.5, 0.5])
scene.add_point_light([1, 2, 2], [1, 1, 1])
scene.add_point_light([1, -2, 2], [1, 1, 1])
scene.add_point_light([-1, 0, 1], [1, 1, 1])

controller = sapien.VulkanController(renderer)
controller.set_current_scene(scene)
controller.set_free_camera_position(-3, 0, 0)

loader = scene.create_urdf_loader()
loader.fix_root_link = 1
asset = loader.load_kinematic(urdf)
assert asset, "No SAPIEN asset is loaded"

while not controller.is_closed:
    scene.step()
    scene.update_render()
    controller.render()

controller = None
scene = None

Use custom shaders

Vulkan renderer uses the spir-v shader format, which can be compiled from other shader languages. Here we will introduce how to compile glsl shaders and use them in SAPIEN. First download the original shader pack original.zip and unzip.

This renderer uses a deferred shading pipeline with forward transparency pass. Files starting with gbuffer processes opaque geometry to create gbuffer textures. Files starting with deferred computes lighting from the gbuffer textures. Files starting with transparency computes another forward transparency pass after deferred shading. Files starting with axis are for axis drawing in the GUI. Files staring with composite are also for displaying to the GUI window.

To compile glsl to spir-v, you will need a tool called glslc. After installing glslc you just need to run glslc -c * in the shader folder containing glsl files to compile them into spir-v shader files. Now to use the shaders, you just need to set the shader path for the VulkanRenderer immediately after its creation.

13
14
renderer = sapien.VulkanRenderer(False)
renderer.set_shader_dir("shader")

Custom shader: world normal

Now let’s try to add the functionality of getting world normal instead of camera space normal. You need to edit the file gbuffer.vert. Change the line objectCoord = pos; as follows.

37
38
  // objectCoord = pos;
  objectCoord = normalize(mat3(transpose(inverse(objectUBO.modelMatrix))) * normal);

You can see, previously, we are storing the local coordinates of the object into the shader variable objectCoord. This variable eventually shows up in the custom texture. Now we just change it to show world normal.

Now in the SAPIEN controller GUI, if you switch to the “custom” display mode. You can see world normals on the object. You can compare it with the camera-space normal shown in the “normal” display mode. If you have a mounted camera, you can get this texture by calling camera.get_custom_rgba().

Per-object data

SAPIEN also supports passing per-object constant into the shader. This enables per-object dynamic effects.

First, to pass in custom data, we need to call set_custom_data on an VisualBody. For any Actor, we call get_visual_bodies to get all visual bodies associated with this actor. The function set_custom_data should always send in a list of 16 numbers representing a 4x4 matrix, but you do not have to use it as a matrix.

For example, the following code sends in increasing values from 0 to 1 into every visual body of the loaded articulation.

34
35
36
37
38
39
40
41
42
43
44
45
46
idx = 0
values = np.linspace(0, 1)

while not controller.is_closed:
    scene.step()
    scene.update_render()
    controller.render()

    for link in asset.get_base_links():
        visual_bodies = link.get_visual_bodies()
        for v in visual_bodies:
            v.set_custom_data([values[idx % len(values)]] + [0] * 15)
    idx += 1

Now let’s adjust the shaders to display some light intensity changes. First we make the following changes to the gbuffer.vert file.

28
29
// layout(location = 3) out vec3 objectCoord;
layout(location = 3) out float intensity;
38
39
  // objectCoord = pos;
  intensity = objectUBO.userData[0][0];

Here we pass a intensity value from the per-object user data into the fragment shader. Now in fragment shader gbuffer.frag, we modify the albedo computation by

38
39
40
41
42
  if (material.hasColorTexture != 0) {
    outAlbedo = texture(colorTexture, inUV) * intensity;
  } else {
    outAlbedo = material.baseColor * intensity;
  }

Now you should see a chair with changing light intensity.