Passing an array to a shader?
Asked Answered
F

13

0

I can post my code if you wish. Do you need a shader part too?

Regarding performance, I think the main bottleneck is in setting the texture pixels on the cpu side. Passing an array may be faster. I'm just guessing. It depends on implementation. Some of the developers here may be able to tell precisely... But if the cpu is writing a lot of individual pixels into a texture each frame, it will surely slow things down considerably, more so if it's done from a script.

Fujio answered 30/9, 2021 at 7:5 Comment(0)
S
0

I haven't tried it, but creating a texture in a script every frame sounds very slow. If all your voxel code is in the shader (meaning you can't cull blocks on the CPU), then you are better off just passing the camera transform as a uniform and doing the cull calculation in the shader. That should be faster.

Sigfried answered 30/9, 2021 at 7:34 Comment(0)
F
0

@cybereality said: I haven't tried it, but creating a texture in a script every frame sounds very slow.

No need to create an actual texture every frame but writing to it each frame is slow as well. It may be an option if the texture is fairly small, like 32x32 or so, which could be enough for what is needed here.

But yeah, the approach seems dubious. Cull the block in the shader while it's being drawn. Iterating all the time through all blocks in the shader is a bit excessive.

Fujio answered 30/9, 2021 at 8:32 Comment(0)
P
0

@xyz sure shader code would be nice too! @cybereality I'd like to do the calculation in the shader, but I would need some way of determining if a pixel obscures another pixel. I believe unity has a stencil buffer in their shader which would do this, but Godot does not.

Do you know of anyway that I could do this in Godot?

I should add that its more complex than just hiding whats above the sprite. The sprite can only move along a single plane at a fixed height (no jumping or falling). So I need any voxels that lie on this plane to be fully visible. I.e. I need to discard all pixels that obscure the voxels that the player can walk on.

Is there a function where I can input a point in world space and determine if material exists at that point?

Pampas answered 30/9, 2021 at 13:49 Comment(0)
F
0

Why not just discard fragments that are closer to the camera than the player sprite and higher than the current walking plane?

Or alternatively, if your blocks are geometry instances, and you already know which blocks are not visible, why not just pass the visibility info via instance data to the shader?

Fujio answered 30/9, 2021 at 14:46 Comment(0)
P
0

@xyz Because of this case:

The plane the sprite is on is only partially obscured by the lower section of brown logs, so in this case I'd like to hide just those brown logs and nothing else.

If we discarded the pixels that were closer to the camera than the sprite and higher than the walking plane then the whole rest of the hill would be discarded when it could still fit on the screen.

However, in the case where the hill is more of a mountain then I'd like to discard the whole thing...

Pampas answered 30/9, 2021 at 14:58 Comment(0)
F
0

Ah, you didn't mention it's isometric. What about the second solution? Send visibility to shader with the block itself if you already know it.

Btw discarding only blocks that cover the player may still look odd and confusing.

Fujio answered 30/9, 2021 at 15:0 Comment(0)
P
0

@xyz oh, I didnt notice the second solution. I'm not sure I understand it. I wont actually know the boundaries of the plane before hand since the game is fully editable in real time like minecraft. So I have to calculate which blocks are visible, every time the camera moves or a new block is placed

Pampas answered 30/9, 2021 at 15:5 Comment(0)
F
0

What's the data you initially wanted to send to the shader via uniform array?

So you're iterating through all blocks whenever something changes to determine visibility? If you're doing that then you can just send that visibility info along with the block to the sader. I'm assuming you're using MultiMeshInstance for blocks. You can send custom 4 floats per mesh instance to the shader. Why not use that to send visibility info?

Fujio answered 30/9, 2021 at 15:7 Comment(0)
P
0

I'm honestly not even sure yet. I was just trying to figure out if it were possible.

I figured I'd start with an array of voxels that I want to be visible. So just a Vector3 for each voxel. and then for each pixel I'd do some math to figure out if a ray going through the camera and the pixel hits any of my voxels, and if it does, then I discard it.

If it matters, I'm not using isometric, I'm using a very zoomed in perspective, so the camera should have one point of origin.

Maybe I should just do this in the cpu and forget about shaders?

Also, thanks so much for the help! I really appreciate it

Pampas answered 30/9, 2021 at 15:13 Comment(0)
F
0

To be honest I'd completely ditch block deletion. It'll look confusing. And I'm still not sure I understand what's the exact criteria for removing a block.

How about drawing the player with 50% opacity if they're behind the block? Or something like that.

Fujio answered 30/9, 2021 at 15:48 Comment(0)
P
0

@xyz you might be right, I'd still like to try block deletion though. Thanks again for the feedback. Can I still see your example code? Also, I dont allow interaction with blocks above the current plane so it may not be as confusing as you think. its really just for aesthetics

Pampas answered 1/10, 2021 at 14:54 Comment(0)
F
0

Sure. Cpu part:


func makeTexture(width, vectors): # vectors is array of Color objects
	var w = width
	var h = max(w, int( floor(vectors.size() / w)) + 1)
	var img = Image.new()
	img.create(w, h, false, Image.FORMAT_RGBAF)
    	
    # put data into image
    img.lock()
    var x: int
    var y: int
    for i in range(vectors.size()):
    	y = i / w
    	x = i % w
    	img.set_pixel(x, y, vectors[i])
    img.unlock()
    	
    var tex = ImageTexture.new()
    tex.create_from_image(img)
    return tex
        	
var vectors = [] 
# fill vectors array with Color objects here
         
var texture = makeTexture(64, vectors) 
material.set_shader_param("vectors", tex)
material.set_shader_param("vectorsTextureWidth", 64)
material.set_shader_param("vectorsCount", vectors.size())

Gpu part:

shader_type canvas_item;

uniform sampler2D vectors;
uniform int vectorsTextureWidth;
uniform int vectorsCount;

void fragment(){
 for(int i=0; i<vectorsCount; ++i){
		coord.x = i % vectorsTextureWidth;
		coord.y = i / vectorsTextureWidth;
		tex = texelFetch(vectors, coord, 0);
		vec3 v = tex.xyz;
		# v is your vector, do whatever you wish with it here
	}
}

This code is not tested. I blindly cut stuff out and renamed some variables. So watch out. But this is it in principle.

EDIT: I forgot to put code in shader inside fragment() function. Corrected this now. And have in mind this is highly inefficient. I was doing this only once at the startup. Updating texture each frame on the cpu and iterating through it for each pixel in the fragment shader could eat up a lot of frame time.

Fujio answered 1/10, 2021 at 15:26 Comment(0)
S
0

@xyz said: Sure. Cpu part:


func makeTexture(width, vectors): # vectors is array of Color objects
	var w = width
	var h = max(w, int( floor(vectors.size() / w)) + 1)
	var img = Image.new()
	img.create(w, h, false, Image.FORMAT_RGBAF)
    	
    # put data into image
    img.lock()
    var x: int
    var y: int
    for i in range(vectors.size()):
    	y = i / w
    	x = i % w
    	img.set_pixel(x, y, vectors[i])
    img.unlock()
    	
    var tex = ImageTexture.new()
    tex.create_from_image(img)
    return tex
        	
var vectors = [] 
# fill vectors array with Color objects here
         
var texture = makeTexture(64, vectors) 
material.set_shader_param("vectors", tex)
material.set_shader_param("vectorsTextureWidth", 64)
material.set_shader_param("vectorsCount", vectors.size())

Gpu part:

shader_type canvas_item;

uniform sampler2D vectors;
uniform int vectorsTextureWidth;
uniform int vectorsCount;

void fragment(){
 for(int i=0; i<vectorsCount; ++i){
		coord.x = i % vectorsTextureWidth;
		coord.y = i / vectorsTextureWidth;
		tex = texelFetch(vectors, coord, 0);
		vec3 v = tex.xyz;
		# v is your vector, do whatever you wish with it here
	}
}

This code is not tested. I blindly cut stuff out and renamed some variables. So watch out. But this is it in principle.

EDIT: I forgot to put code in shader inside fragment() function. Corrected this now. And have in mind this is highly inefficient. I was doing this only once at the startup. Updating texture each frame on the cpu and iterating through it for each pixel in the fragment shader could eat up a lot of frame time.

Have you tried exporting the project using this shader to html? I found that texelFetch doesn't seem to read the correct values on the browser, but it doesn't have any problems when running on window.

Size answered 20/4, 2022 at 8:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.